Documentation ¶
Overview ¶
Package clog is a structured logger optimized for Cloud Logging based on log/slog.
clog supports following special fields in JSON log entries:
- severity
- message
- httpRequest
- time
- logging.googleapis.com/insertId
- logging.googleapis.com/labels
- logging.googleapis.com/operation
- logging.googleapis.com/sourceLocation
- logging.googleapis.com/spanId
- logging.googleapis.com/trace
- logging.googleapis.com/trace_sampled
- logging.googleapis.com/stack_trace
See Cloud Logging documentation and Cloud Error Reporting documentation for more details.
Severity ¶
clod uses Severity in the "severity" field instead of log levels. 8 severities are supported:
- DEBUG
- INFO
- NOTICE
- WARNING
- ERROR
- CRITICAL
- ALERT
- EMERGENCY
Usage ¶
Each severity has three methods like Info, Infof, and InfoErr.
clog.Info(ctx, "simple logging with args", "key", "value") // {"severity":"INFO", "message":"simple logging with args", "key":"value", // "time": "...", "logging.googleapis.com/sourceLocation": {...}} clog.Noticef(ctx, "formatted message %s %s", "like", "fmt.Printf") // {"severity":"NOTICE", "message":"formatted message like fmt.Printf", // "time": "...", "logging.googleapis.com/sourceLocation": {...}} clog.CriticalErr(ctx, errors.New("critical error!!"), "key", "value") // {"severity":"CRITICAL", "message":"critical error!!", "key":"value", // "time": "...", "logging.googleapis.com/sourceLocation": {...}} // clog.ErrorErr has a shorthand clog.Err. clog.Err(ctx, errors.New("error!")) // {"severity":"ERROR", "message":"error!", // "time": "...", "logging.googleapis.com/sourceLocation": {...}}
See [Examples] for more details.
Example ¶
package main import ( "context" "log/slog" "net/http" "net/http/httptest" "os" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" "go.nownabe.dev/clog" "go.nownabe.dev/clog/errors" ) type ctxUserIDKey struct{} func InitializeClog() { // You can add labels to classify logs. appLabels := map[string]string{ "app": "myapp", "version": "1.0.0", } // Your Google Cloud Project ID. projectID := "my-gcp-project" // Custome handler to add user ID to logs. customHandler := func(next clog.HandleFunc) clog.HandleFunc { return func(ctx context.Context, r slog.Record) error { if ctx == nil { return next(ctx, r) } if userID, ok := ctx.Value(ctxUserIDKey{}).(string); ok { r.AddAttrs(slog.String("user_id", userID)) } return next(ctx, r) } } // Create a custom logger. logger := clog.New(os.Stdout, clog.SeverityInfo, true, clog.WithLabels(appLabels), // Add logger-level labels to classify logs. clog.WithTrace(projectID), // Add trace and span ID to logs. clog.WithHandleFunc(customHandler), // Add custom log handler. ) // You can add custome fields to logs. logger = logger.With("key1", "value1", "key2", "value2") clog.SetDefault(logger) } func RequestHandler(w http.ResponseWriter, r *http.Request) { ctx := r.Context() tracer := otel.Tracer("tracer") ctx, span := tracer.Start(ctx, "RequestHandler") defer span.End() ctx, removeLabel := clog.ContextWithLabel(ctx, "handler", "RequestHandler") defer removeLabel() clog.Info(ctx, "request received") /* { "time": "2023-09-18T20:46:03.448753224+09:00", "severity": "INFO", "message": "request received", "logging.googleapis.com/sourceLocation": { "file": "go.nownabe.dev/clog/example_test.go", "line": "66", "function": "main.RequestHandler" }, "user_id": "user1", "logging.googleapis.com/trace": "projects/my-gcp-project/traces/abcdabcdabcdabcdabcdabcdabcdabcd", "logging.googleapis.com/spanId": "1234123412341234", "logging.googleapis.com/trace_sampled": false, "logging.googleapis.com/labels": { "handler": "RequestHandler", "app": "myapp", "version": "1.0.0" } } */ defer clog.Info(ctx, "request processed") err := errors.New("something went wrong") if err != nil { clog.Err(ctx, err) /* { "time": "2023-09-18T21:14:02.980412394+09:00", "severity": "ERROR", "message": "something went wrong", "stack_trace": "something went wrong\n\ngoroutine 0 [running]:\nmain.RequestHandler(...)\n\tgo.nownabe.dev/clog/example_test.go:90\nnet/http.HandlerFunc.ServeHTTP(...)\n\tnet/http/server.go:2136\nmain.Example(...)\n\tgo.nownabe.dev/clog/example_test.go:135\nmain.main(...)\n\tgo.nownabe.dev/clog/main.go:20\nruntime.main(...)\n\truntime/proc.go:267\n", "logging.googleapis.com/sourceLocation": { "file": "go.nownabe.dev/clog/example_test.go", "line": "92", "function": "main.RequestHandler" }, "user_id": "user1", "logging.googleapis.com/trace": "projects/my-gcp-project/traces/abcdabcdabcdabcdabcdabcdabcdabcd", "logging.googleapis.com/spanId": "1234123412341234", "logging.googleapis.com/trace_sampled": false, "logging.googleapis.com/labels": { "handler": "RequestHandler", "app": "myapp", "version": "1.0.0" } } */ http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return /* { "time": "2023-09-18T21:14:02.980418868+09:00", "severity": "INFO", "message": "request processed", "logging.googleapis.com/sourceLocation": { "file": "go.nownabe.dev/clog/example_test.go", "line": "116", "function": "main.RequestHandler" }, "user_id": "user1", "logging.googleapis.com/trace": "projects/my-gcp-project/traces/abcdabcdabcdabcdabcdabcdabcdabcd", "logging.googleapis.com/spanId": "1234123412341234", "logging.googleapis.com/trace_sampled": false, "logging.googleapis.com/labels": { "handler": "RequestHandler", "app": "myapp", "version": "1.0.0" } } */ } w.Write([]byte("Hello, World!")) } func main() { ctx := context.Background() ctx = withTraceSpan(ctx) ctx = context.WithValue(ctx, ctxUserIDKey{}, "user1") req, err := http.NewRequestWithContext(ctx, http.MethodGet, "", nil) if err != nil { panic(err) } InitializeClog() clog.Notice(ctx, "app started") /* { "time": "2023-09-18T20:46:03.448702881+09:00", "severity": "NOTICE", "message": "app started", "logging.googleapis.com/sourceLocation": { "file": "go.nownabe.dev/clog/example_test.go", "line": "92", "function": "main.Example" }, "user_id": "user1", "logging.googleapis.com/trace": "projects/my-gcp-project/traces/abcdabcdabcdabcdabcdabcdabcdabcd", "logging.googleapis.com/spanId": "1234123412341234", "logging.googleapis.com/trace_sampled": false, "logging.googleapis.com/labels": { "app": "myapp", "version": "1.0.0" } } */ (http.HandlerFunc(RequestHandler)).ServeHTTP(httptest.NewRecorder(), req) clog.Notice(ctx, "app finished") /* { "time": "2023-09-18T21:14:02.980422814+09:00", "severity": "NOTICE", "message": "app finished", "logging.googleapis.com/sourceLocation": { "file": "go.nownabe.dev/clog/tmp/example.go", "line": "178", "function": "main.Example" }, "user_id": "user1", "logging.googleapis.com/trace": "projects/my-gcp-project/traces/abcdabcdabcdabcdabcdabcdabcdabcd", "logging.googleapis.com/spanId": "1234123412341234", "logging.googleapis.com/trace_sampled": false, "logging.googleapis.com/labels": { "app": "myapp", "version": "1.0.0" } } */ } func withTraceSpan(ctx context.Context) context.Context { traceID, err := trace.TraceIDFromHex("abcdabcdabcdabcdabcdabcdabcdabcd") if err != nil { panic(err) } spanID, err := trace.SpanIDFromHex("1234123412341234") if err != nil { panic(err) } cfg := trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, } sc := trace.NewSpanContext(cfg) return trace.ContextWithSpanContext(ctx, sc) }
Output:
Index ¶
- Constants
- func Alert(ctx context.Context, msg string, args ...any)
- func AlertErr(ctx context.Context, err error, args ...any)
- func Alertf(ctx context.Context, format string, a ...any)
- func ContextWithLabel(ctx context.Context, key string, value string) (context.Context, func())
- func Critical(ctx context.Context, msg string, args ...any)
- func CriticalErr(ctx context.Context, err error, args ...any)
- func Criticalf(ctx context.Context, format string, a ...any)
- func Debug(ctx context.Context, msg string, args ...any)
- func DebugErr(ctx context.Context, err error, args ...any)
- func Debugf(ctx context.Context, format string, a ...any)
- func Emergency(ctx context.Context, msg string, args ...any)
- func EmergencyErr(ctx context.Context, err error, args ...any)
- func Emergencyf(ctx context.Context, format string, a ...any)
- func Enabled(ctx context.Context, s Severity) bool
- func Err(ctx context.Context, err error, args ...any)
- func Error(ctx context.Context, msg string, args ...any)
- func ErrorErr(ctx context.Context, err error, args ...any)
- func Errorf(ctx context.Context, format string, a ...any)
- func HTTPReq(ctx context.Context, req *HTTPRequest, args ...any)
- func Info(ctx context.Context, msg string, args ...any)
- func InfoErr(ctx context.Context, err error, args ...any)
- func Infof(ctx context.Context, format string, a ...any)
- func Log(ctx context.Context, s Severity, msg string, args ...any)
- func Notice(ctx context.Context, msg string, args ...any)
- func NoticeErr(ctx context.Context, err error, args ...any)
- func Noticef(ctx context.Context, format string, a ...any)
- func SetDefault(l *Logger)
- func SetOptions(opts ...Option)
- func StartOperation(ctx context.Context, s Severity, msg, id, producer string) (context.Context, func(msg string))
- func Warning(ctx context.Context, msg string, args ...any)
- func WarningErr(ctx context.Context, err error, args ...any)
- func Warningf(ctx context.Context, format string, a ...any)
- type HTTPRequest
- type HandleFunc
- type Logger
- func (l *Logger) Alert(ctx context.Context, msg string, args ...any)
- func (l *Logger) AlertErr(ctx context.Context, err error, args ...any)
- func (l *Logger) Alertf(ctx context.Context, format string, a ...any)
- func (l *Logger) Critical(ctx context.Context, msg string, args ...any)
- func (l *Logger) CriticalErr(ctx context.Context, err error, args ...any)
- func (l *Logger) Criticalf(ctx context.Context, format string, a ...any)
- func (l *Logger) Debug(ctx context.Context, msg string, args ...any)
- func (l *Logger) DebugErr(ctx context.Context, err error, args ...any)
- func (l *Logger) Debugf(ctx context.Context, format string, a ...any)
- func (l *Logger) Emergency(ctx context.Context, msg string, args ...any)
- func (l *Logger) EmergencyErr(ctx context.Context, err error, args ...any)
- func (l *Logger) Emergencyf(ctx context.Context, format string, a ...any)
- func (l *Logger) Enabled(ctx context.Context, s Severity) bool
- func (l *Logger) Err(ctx context.Context, err error, args ...any)
- func (l *Logger) Error(ctx context.Context, msg string, args ...any)
- func (l *Logger) ErrorErr(ctx context.Context, err error, args ...any)
- func (l *Logger) Errorf(ctx context.Context, format string, a ...any)
- func (l *Logger) HTTPReq(ctx context.Context, req *HTTPRequest, args ...any)
- func (l *Logger) Info(ctx context.Context, msg string, args ...any)
- func (l *Logger) InfoErr(ctx context.Context, err error, args ...any)
- func (l *Logger) Infof(ctx context.Context, format string, a ...any)
- func (l *Logger) Log(ctx context.Context, s Severity, msg string, args ...any)
- func (l *Logger) Notice(ctx context.Context, msg string, args ...any)
- func (l *Logger) NoticeErr(ctx context.Context, err error, args ...any)
- func (l *Logger) Noticef(ctx context.Context, format string, a ...any)
- func (l *Logger) StartOperation(ctx context.Context, s Severity, msg, id, producer string) (context.Context, func(msg string))
- func (l *Logger) Warning(ctx context.Context, msg string, args ...any)
- func (l *Logger) WarningErr(ctx context.Context, err error, args ...any)
- func (l *Logger) Warningf(ctx context.Context, format string, a ...any)
- func (l *Logger) With(args ...any) *Logger
- func (l *Logger) WithInsertID(id string) *Logger
- type Option
- type Severity
Examples ¶
Constants ¶
const ( // SeverityDefault indicates that the log entry has no assigned severity level. SeverityDefault = Severity(0) // SeverityDebug indicates debug or trace information. SeverityDebug = Severity(100) // SeverityInfo indicates routine information, such as ongoing status or performance. SeverityInfo = Severity(200) // SeverityNotice indicates normal but significant events, such as start up, shut down, or a configuration change. SeverityNotice = Severity(300) // SeverityWarning indicates warning events that might cause problems. SeverityWarning = Severity(400) // SeverityError indicates error events that are likely to cause problems. SeverityError = Severity(500) // SeverityCritical indicates critical events that cause more severe problems or outages. SeverityCritical = Severity(600) // SeverityAlert indicates that a person must take an action immediately. SeverityAlert = Severity(700) // SeverityEmergency indicates that one or more systems are unusable. SeverityEmergency = Severity(800) )
Variables ¶
This section is empty.
Functions ¶
func ContextWithLabel ¶
ContextWithLabel returns a new context with the label that consists of given key and value. See https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry
func CriticalErr ¶
CriticalErr logs an error at SeverityCriticaDefault().
func EmergencyErr ¶
EmergencyErr logs an error at SeverityEmergency.
func Emergencyf ¶
Emergencyf logs formatted in the manner of fmt.Printf at SeverityEmergency.
func Enabled ¶
Enabled reports whether the Logger emits log records at the given context and leveDefault().
func HTTPReq ¶
func HTTPReq(ctx context.Context, req *HTTPRequest, args ...any)
HTTPReq emits a log with the given HTTPRequest. The value of message field will be like "GET /foo HTTP/1.1". If status >= 500, the log is at SeverityError. Otherwise, the log is at SeverityInfo.
Example ¶
package main import ( "context" "time" "go.nownabe.dev/clog" ) func main() { req := &clog.HTTPRequest{ RequestMethod: "GET", RequestURL: "https://example.com/foo", RequestSize: 123, Status: 200, ResponseSize: 456, UserAgent: "clog", RemoteIP: "203.0.113.1", ServerIP: "203.0.113.2", Referer: "https://example.com/referer", Latency: 123*time.Second + 456*time.Nanosecond, CacheLookup: true, CacheHit: true, CacheValidatedWithOriginServer: true, CacheFillBytes: 789, Protocol: "HTTP/1.1", } clog.HTTPReq(context.Background(), req, "GET /foo") }
Output:
func StartOperation ¶
func StartOperation(ctx context.Context, s Severity, msg, id, producer string) (context.Context, func(msg string))
StartOperation returns a new context and a function to end the opration, starting the operation. See https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#LogEntryOperation
Example ¶
package main import ( "context" "go.nownabe.dev/clog" ) func DoLongRunningOperation(ctx context.Context) { ctx, end := clog.StartOperation(ctx, clog.SeverityInfo, "long-running operation started", "long-running", "my-app") defer end("long-running operation ended") clog.Info(ctx, "long-running operation is running") } func main() { DoLongRunningOperation(context.Background()) // Logs are like: // {"severity":"INFO", "message":"long-running operation started", // "logging.googleapis.com/operation":{"id":"long-running","producer":"my-app","first":true}, ...} // {"severity":"INFO", "message":"long-running operation is running", // "logging.googleapis.com/operation":{"id":"long-running","producer":"my-app"}, ...} // {"severity":"INFO", "message":"long-running operation ended", // "logging.googleapis.com/operation":{"id":"long-running","producer":"my-app","last":true}, ...} }
Output:
func WarningErr ¶
WarningErr logs an error at SeverityWarning.
Types ¶
type HTTPRequest ¶
type HTTPRequest struct { RequestMethod string RequestURL string RequestSize int64 Status int ResponseSize int64 UserAgent string RemoteIP string ServerIP string Referer string Latency time.Duration CacheLookup bool CacheHit bool CacheValidatedWithOriginServer bool CacheFillBytes int64 Protocol string }
HTTPRequest represents HttpRequest. See these links: https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#HttpRequest, https://github.com/googleapis/googleapis/blob/master/google/logging/type/http_request.proto.
If HTTPRequest is empty, it will be omitted. cf. https://cs.opensource.google/go/go/+/refs/tags/go1.21.1:src/log/slog/record.go;l=118
func (*HTTPRequest) LogValue ¶
func (r *HTTPRequest) LogValue() slog.Value
LogValue returns slog.Value.
type Logger ¶
type Logger struct {
// contains filtered or unexported fields
}
func WithInsertID ¶
WithInsertID returns a Logger that includes the given insertId in each output operation. See https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry
func (*Logger) CriticalErr ¶
CriticalErr logs an error at SeverityCritical.
func (*Logger) Criticalf ¶
Criticalf logs formatted in the manner of fmt.Printf at SeverityCritical.
func (*Logger) EmergencyErr ¶
EmergencyErr logs an error at SeverityEmergency.
func (*Logger) Emergencyf ¶
Emergencyf logs formatted in the manner of fmt.Printf at SeverityEmergency.
func (*Logger) Enabled ¶
Enabled reports whether the Logger emits log records at the given context and level.
func (*Logger) HTTPReq ¶
func (l *Logger) HTTPReq(ctx context.Context, req *HTTPRequest, args ...any)
HTTPReq emits a log with the given HTTPRequest. The value of message field will be like "GET /foo HTTP/1.1". If status >= 500, the log is at SeverityError. Otherwise, the log is at SeverityInfo.
func (*Logger) StartOperation ¶
func (l *Logger) StartOperation( ctx context.Context, s Severity, msg, id, producer string, ) (context.Context, func(msg string))
StartOperation returns a new context and a function to end the opration, starting the operation. See https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#LogEntryOperation
func (*Logger) WarningErr ¶
WarningErr logs an error at SeverityWarning.
func (*Logger) With ¶
With returns a Logger that includes the given attributes in each output operation.
func (*Logger) WithInsertID ¶
WithInsertID returns a Logger that includes the given insertId in each output operation. See https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry
type Option ¶
type Option interface {
// contains filtered or unexported methods
}
func WithHandleFunc ¶
func WithHandleFunc(f func(next HandleFunc) HandleFunc) Option
func WithLabels ¶
WithLabels returns an Option that sets the default labels. See https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry
type Severity ¶
Severity is the severity of the log event. These severities are defined in the Cloud Logging API v2 as an enum.
See following links. https://github.com/googleapis/googleapis/blob/master/google/logging/type/log_severity.proto https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#LogSeverity
Though these packages provide predefined severity constants, we don't use them not to depend on external package just for it. https://pkg.go.dev/google.golang.org/genproto/googleapis/logging/type#LogSeverity https://pkg.go.dev/cloud.google.com/go/logging#Severity