diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index a872c6c..e9e0fb6 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -10,7 +10,7 @@ on: # To guarantee Maintained check is occasionally updated. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained #schedule: - #- cron: '18 16 * * 2' + #- cron: '28 21 * * 1' push: branches: [ "main" ] @@ -26,40 +26,32 @@ jobs: security-events: write # Needed to publish results and get a badge (see publish_results below). id-token: write - # Uncomment the permissions below if installing in a private repository. - # contents: read - # actions: read steps: - name: "Checkout code" - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@e38b1902ae4f44df626f11ba0734b14fb91f8f86 # v2.1.2 + uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1 with: results_file: results.sarif results_format: sarif # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: - # - you want to enable the Branch-Protection check on a *public* repository, or - # - you are installing Scorecard on a *private* repository + # you want to enable the Branch-Protection check on a *public* repository, or # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. # repo_token: ${{ secrets.SCORECARD_TOKEN }} - # Public repositories: - # - Publish results to OpenSSF REST API for easy access by consumers - # - Allows the repository to include the Scorecard badge. - # - See https://github.com/ossf/scorecard-action#publishing-results. - # For private repositories: - # - `publish_results` will always be set to `false`, regardless - # of the value entered here. + # - Publish results to OpenSSF REST API for easy access by consumers + # - Allows the repository to include the Scorecard badge. + # - See https://github.com/ossf/scorecard-action#publishing-results. publish_results: true # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0 + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: name: SARIF file path: results.sarif @@ -67,6 +59,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2.2.4 + uses: github/codeql-action/upload-sarif@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 with: sarif_file: results.sarif diff --git a/README.md b/README.md index 87db3cd..7a76a73 100644 --- a/README.md +++ b/README.md @@ -51,8 +51,9 @@ down the stack and with any libraries that use either slog-context or logr. ### Other Great SLOG Utilities - [slogctx](https://github.com/veqryn/slog-context): Add attributes to context and have them automatically added to all log lines. Work with a logger stored in context. - [slogotel](https://github.com/veqryn/slog-context/tree/main/otel): Automatically extract and add [OpenTelemetry](https://opentelemetry.io/) TraceID's to all log lines. -- [slogdedup](https://github.com/veqryn/slog-dedup): Middleware that deduplicates and sorts attributes. Particularly useful for JSON logging. +- [slogdedup](https://github.com/veqryn/slog-dedup): Middleware that deduplicates and sorts attributes. Particularly useful for JSON logging. Format logs for aggregators (Graylog, GCP/Stackdriver, etc). - [slogbugsnag](https://github.com/veqryn/slog-bugsnag): Middleware that pipes Errors to [Bugsnag](https://www.bugsnag.com/). +- [slogjson](https://github.com/veqryn/slog-json): Formatter that uses the [JSON v2](https://github.com/golang/go/discussions/63397) [library](https://github.com/go-json-experiment/json), with optional single-line pretty-printing. ## Install @@ -73,6 +74,7 @@ package main import ( "context" + "errors" "log/slog" "os" @@ -96,7 +98,7 @@ import ( // logger and its attributes will propagate with it, adding these to any log // lines using that context. func main() { - h := slog.NewJSONHandler(os.Stdout, nil) + h := slogctx.NewHandler(slog.NewJSONHandler(os.Stdout, nil), nil) slog.SetDefault(slog.New(h)) // Store the logger inside the context: @@ -123,17 +125,22 @@ func main() { // and can take a mix of slog.Attr and key-value pairs. ctx = slogctx.With(ctx, slog.String("subKey", "subValue"), slog.Bool("someBool", true)) + err := errors.New("an error") + // Access the logger in the context directly with handy wrappers for Debug/Info/Warn/Error/Log/LogAttrs: - slogctx.Info(ctx, "main message", "mainKey", "mainValue") + slogctx.Error(ctx, "main message", + slogctx.Err(err), + slog.String("mainKey", "mainValue")) /* { "time":"2023-11-14T00:53:46.363072-07:00", - "level":"INFO", + "level":"ERROR", "msg":"main message", "rootKey":"rootValue", "someGroup":{ "subKey":"subValue", "someBool":true, + "err":"an error", "mainKey":"mainValue" } } diff --git a/ctx_test.go b/ctx_test.go index 39fb487..0c630bd 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -3,6 +3,7 @@ package slogctx import ( "bytes" "context" + "errors" "log/slog" "testing" ) @@ -80,8 +81,8 @@ func TestCtx(t *testing.T) { } buf.Reset() - Error(ctx, "main message", "main1", "arg1", "main1", "arg2") - expectedError := `{"time":"2023-09-29T13:00:59Z","level":"ERROR","msg":"main message","with1":"arg1","with1":"arg2","with2":"arg1","with2":"arg2","group1":{"with4":"arg1","with4":"arg2","with5":"arg1","with5":"arg2","main1":"arg1","main1":"arg2"}} + Error(ctx, "main message", "main1", "arg1", "main1", "arg2", Err(errors.New("an error"))) + expectedError := `{"time":"2023-09-29T13:00:59Z","level":"ERROR","msg":"main message","with1":"arg1","with1":"arg2","with2":"arg1","with2":"arg2","group1":{"with4":"arg1","with4":"arg2","with5":"arg1","with5":"arg2","main1":"arg1","main1":"arg2","err":"an error"}} ` if buf.String() != expectedError { t.Errorf("Expected:\n%s\nGot:\n%s\n", expectedError, buf.String()) diff --git a/examples/logger-in-ctx/logger-in-ctx-main.go b/examples/logger-in-ctx/logger-in-ctx-main.go index b226c32..a8e7598 100644 --- a/examples/logger-in-ctx/logger-in-ctx-main.go +++ b/examples/logger-in-ctx/logger-in-ctx-main.go @@ -2,6 +2,7 @@ package main import ( "context" + "errors" "log/slog" "os" @@ -25,7 +26,7 @@ import ( // logger and its attributes will propagate with it, adding these to any log // lines using that context. func main() { - h := slog.NewJSONHandler(os.Stdout, nil) + h := slogctx.NewHandler(slog.NewJSONHandler(os.Stdout, nil), nil) slog.SetDefault(slog.New(h)) // Store the logger inside the context: @@ -52,17 +53,22 @@ func main() { // and can take a mix of slog.Attr and key-value pairs. ctx = slogctx.With(ctx, slog.String("subKey", "subValue"), slog.Bool("someBool", true)) + err := errors.New("an error") + // Access the logger in the context directly with handy wrappers for Debug/Info/Warn/Error/Log/LogAttrs: - slogctx.Info(ctx, "main message", "mainKey", "mainValue") + slogctx.Error(ctx, "main message", + slogctx.Err(err), + slog.String("mainKey", "mainValue")) /* { "time":"2023-11-14T00:53:46.363072-07:00", - "level":"INFO", + "level":"ERROR", "msg":"main message", "rootKey":"rootValue", "someGroup":{ "subKey":"subValue", "someBool":true, + "err":"an error", "mainKey":"mainValue" } } diff --git a/examples_test.go b/examples_test.go index 0f8882c..ebf642b 100644 --- a/examples_test.go +++ b/examples_test.go @@ -2,6 +2,7 @@ package slogctx_test import ( "context" + "errors" "log/slog" "os" @@ -56,7 +57,7 @@ func ExampleNewHandler() { // Use the logger like normal; add attributes, create groups, pass it around: log := slog.With("rootKey", "rootValue") log = log.WithGroup("someGroup") - log = log.With("subKey", "subValue8") + log = log.With("subKey", "subValue") // The prepended/appended attributes end up in all log lines that use that context log.InfoContext(ctx, "main message", "mainKey", "mainValue") @@ -94,7 +95,7 @@ func ExampleNewCtx() { // logger and its attributes will propagate with it, adding these to any log // lines using that context. - h := slog.NewJSONHandler(os.Stdout, nil) + h := slogctx.NewHandler(slog.NewJSONHandler(os.Stdout, nil), nil) slog.SetDefault(slog.New(h)) // Store the logger inside the context: @@ -103,6 +104,13 @@ func ExampleNewCtx() { // Get the logger back out again at any time, for manual usage: log := slogctx.FromCtx(ctx) log.Warn("warning") + /* + { + "time":"2023-11-14T00:53:46.361201-07:00", + "level":"INFO", + "msg":"warning" + } + */ // Add attributes directly to the logger in the context: ctx = slogctx.With(ctx, "rootKey", "rootValue") @@ -112,18 +120,24 @@ func ExampleNewCtx() { // With and wrapper methods have the same args signature as slog methods, // and can take a mix of slog.Attr and key-value pairs. - ctx = slogctx.With(ctx, slog.String("subKey", "subValue")) + ctx = slogctx.With(ctx, slog.String("subKey", "subValue"), slog.Bool("someBool", true)) + + err := errors.New("an error") // Access the logger in the context directly with handy wrappers for Debug/Info/Warn/Error/Log/LogAttrs: - slogctx.Info(ctx, "main message", "mainKey", "mainValue") + slogctx.Error(ctx, "main message", + slogctx.Err(err), + slog.String("mainKey", "mainValue")) /* { "time":"2023-11-14T00:53:46.363072-07:00", - "level":"INFO", + "level":"ERROR", "msg":"main message", "rootKey":"rootValue", "someGroup":{ "subKey":"subValue", + "someBool":true, + "err":"an error", "mainKey":"mainValue" } } diff --git a/wrappers.go b/wrappers.go index fb9dbaa..f68229c 100644 --- a/wrappers.go +++ b/wrappers.go @@ -7,6 +7,16 @@ import ( "time" ) +// ErrKey is the key used by handlers for an error +// when the log method is called. The associated Value is an error. +const ErrKey = "err" // TODO: consider making a var to allow changes before. + +// Err is a convenience method that creates a [slog.Attr] out of an error. +// It uses a consistent key: [ErrKey] +func Err(err error) slog.Attr { + return slog.Any(ErrKey, err) +} + // With calls With on the logger stored in the context, // or if there isn't any, on the default logger. // This new logger is stored in a child context and the new context is returned.