Skip to content

Commit

Permalink
Add a new format, JSON_GCP (#61)
Browse files Browse the repository at this point in the history
When deploying a service that sends out logs to cloud logging, the
severity was not parsed and because logs of our services are all sent
out of STDERR, they all defaulted to `severity: "ERROR"`, making it
quite hard to read.

This PRs adds a new `SRC_LOG_FORMAT=json_gcp`, which outputs json logs
in a structure that cloud logging understands.

See the results of a little test in Cloud run: 


![image](https://github.com/sourcegraph/log/assets/10151/88b81ae5-936f-4f63-a22e-7ad7796151e2)
  • Loading branch information
jhchabran committed Jul 11, 2023
1 parent ad2d71b commit 40c57b6
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 10 deletions.
2 changes: 1 addition & 1 deletion init.go
Expand Up @@ -14,7 +14,7 @@ var (
// EnvLogFormat is key of the environment variable that is used to set the log format
// on Init.
//
// The value should be one of 'json' or 'console', defaulting to 'json'.
// The value should be one of 'json', 'json_gcp' or 'condensed', defaulting to 'json'.
EnvLogFormat = "SRC_LOG_FORMAT"
// EnvLogLevel is key of the environment variable that can be used to set the log
// level on Init.
Expand Down
27 changes: 27 additions & 0 deletions internal/encoders/config.go
Expand Up @@ -38,6 +38,31 @@ var OpenTelemetryConfig = zapcore.EncoderConfig{
EncodeCaller: zapcore.ShortCallerEncoder,
}

var GCPConfig = zapcore.EncoderConfig{
NameKey: "InstrumentationScope",
// https://cloud.google.com/logging/docs/agent/logging/configuration#timestamp-processing
TimeKey: "timestampNanos",
EncodeTime: zapcore.EpochNanosTimeEncoder,
// https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#logseverity
// https://cloud.google.com/logging/docs/agent/logging/configuration#special-fields
LevelKey: "severity",
EncodeLevel: zapcore.CapitalLevelEncoder, // most levels correspond to the OT level text
// https://cloud.google.com/logging/docs/agent/logging/configuration#special-fields
MessageKey: "message",

// These don't really have an equivalent in the OT spec, and we can't stick it under
// Attributes because they are top-level traits in Zap, so we just capitalize them and
// hope for the best.
CallerKey: "Caller",
FunctionKey: "Function",
StacktraceKey: "Stacktrace",

// Defaults
LineEnding: zapcore.DefaultLineEnding,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
}

// applyDevConfig applies options for dev environments to the encoder config
func applyDevConfig(cfg zapcore.EncoderConfig) zapcore.EncoderConfig {
// Nice colors based on log level
Expand Down Expand Up @@ -75,6 +100,8 @@ func BuildEncoder(format output.Format, development bool) (enc zapcore.Encoder)
return zapcore.NewConsoleEncoder(config)
case output.FormatJSON:
return zapcore.NewJSONEncoder(config)
case output.FormatJSONGCP:
return zapcore.NewJSONEncoder(GCPConfig)
default:
panic("unknown output format")
}
Expand Down
24 changes: 15 additions & 9 deletions output/output.go
Expand Up @@ -7,6 +7,10 @@ const (
// FormatJSON encodes log entries to a machine-readable, OpenTelemetry-structured
// format.
FormatJSON Format = "json"
// FormatJSONGCP encodes log entries to a machine-readable, GCP-structured format.
// It's similar to OpenTelemetry-structured format, but the severity field
// complies with https://cloud.google.com/logging/docs/structured-logging#special-payload-fields
FormatJSONGCP Format = "json_gcp"
// FormatConsole encodes log entries to a human-readable format.
FormatConsole Format = "console"
)
Expand All @@ -16,16 +20,18 @@ const (
// log formats.
func ParseFormat(format string) Format {
switch format {
case string(FormatJSON),
// True 'logfmt' has significant limitations around certain field types:
// https://github.com/jsternberg/zap-logfmt#limitations so since it implies a
// desire for a somewhat structured format, we interpret it as OutputJSON.
"logfmt":
case string(FormatJSONGCP):
return FormatJSONGCP

// True 'logfmt' has significant limitations around certain field types:
// https://github.com/jsternberg/zap-logfmt#limitations so since it implies a
// desire for a somewhat structured format, we interpret it as OutputJSON.
case string(FormatJSON), "logfmt":
return FormatJSON
case string(FormatConsole),
// The previous 'condensed' format is optimized for local dev, so it serves the
// same purpose as OutputConsole
"condensed":

// The previous 'condensed' format is optimized for local dev, so it serves the
// same purpose as OutputConsole
case string(FormatConsole), "condensed":
return FormatConsole
}

Expand Down

0 comments on commit 40c57b6

Please sign in to comment.