// Copyright 2016 Google Inc. All rights reserved. // Use of this source code is governed by the Apache 2.0 // license that can be found in the LICENSE file. // +build !appengine package internal import ( "encoding/json" "fmt" "io" "io/ioutil" "log" "os" "strings" "sync" "time" ) // jsonLogger writes logs in the JSON format required for Flex Logging. It can // be used concurrently. type jsonLogger struct { mu sync.Mutex enc *json.Encoder } type logLine struct { Message string `json:"message"` Timestamp logTimestamp `json:"timestamp"` Severity string `json:"severity"` TraceID string `json:"traceId,omitempty"` } type logTimestamp struct { Seconds int64 `json:"seconds"` Nanos int `json:"nanos"` } var ( logger *jsonLogger loggerOnce sync.Once logPath = "/var/log/app_engine/app.json" stderrLogger = newJSONLogger(os.Stderr) testLogger = newJSONLogger(ioutil.Discard) levels = map[int64]string{ 0: "DEBUG", 1: "INFO", 2: "WARNING", 3: "ERROR", 4: "CRITICAL", } ) func globalLogger() *jsonLogger { loggerOnce.Do(func() { f, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) if err != nil { log.Printf("failed to open/create log file, logging to stderr: %v", err) logger = stderrLogger return } logger = newJSONLogger(f) }) return logger } func logf(ctx *context, level int64, format string, args ...interface{}) { s := strings.TrimSpace(fmt.Sprintf(format, args...)) now := time.Now() trace := ctx.req.Header.Get(traceHeader) if i := strings.Index(trace, "/"); i > -1 { trace = trace[:i] } line := &logLine{ Message: s, Timestamp: logTimestamp{ Seconds: now.Unix(), Nanos: now.Nanosecond(), }, Severity: levels[level], TraceID: trace, } if err := ctx.logger.emit(line); err != nil { log.Printf("failed to write log line to file: %v", err) } log.Print(levels[level] + ": " + s) } func newJSONLogger(w io.Writer) *jsonLogger { return &jsonLogger{ enc: json.NewEncoder(w), } } func (l *jsonLogger) emit(line *logLine) error { l.mu.Lock() defer l.mu.Unlock() return l.enc.Encode(line) }