mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-18 05:52:51 +00:00
feat(logging): gcp error reporting (#11355)
# Which Problems Are Solved Zitadel Errors (`zerrors` package) are logged by default. This change add more verbose error reporting through log output, including compatibility with GCP error reporting if enabled. # How the Problems Are Solved Errors can now carry their "report location" and a stack trace. "Report location" is enabled by default, while stack-traces are disabled. Our new [`zitadel/sloggcp`](https://github.com/zitadel/sloggcp) package provides the GCP error reporting handler for `slog`. The following section displays the different formats for the same error (User not found). Newlines and formatting of output is applied for readability and is not part of the actual log output. <details> <summary>Default text output</summary> ``` time=2026-01-15T14:27:20.625+01:00 level=WARN source=/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/gerrors/zitadel_errors.go:57 msg="User could not be found (COMMAND-bd4ir1mblj)" domain.instance_host=localhost:8080 domain.protocol=http protocol=connect service=zitadel.user.v2.UserService http_method=POST path=/zitadel.user.v2.UserService/DeleteUser request_id=d5keme0ednc50mm74bfg duration=66.761569ms TraceID=319374baa56acc2dbd1d0179f1bbec9f SpanID=541621b0abea7ff1 err.kind=NotFound err.message="User could not be found" err.id=COMMAND-bd4ir1mblj err.reportLocation.filePath=/home/tim/Repositories/zitadel/zitadel/internal/command/user_v2.go err.reportLocation.lineNumber=140 err.reportLocation.functionName=github.com/zitadel/zitadel/internal/command.(*Commands).RemoveUserV2 ``` </details> <details> <summary>Default JSON output</summary> ```json { "time": "2026-01-15T14:30:59.280497754+01:00", "level": "WARN", "source": { "function": "github.com/zitadel/zitadel/internal/api/grpc/gerrors.ZITADELToConnectError", "file": "/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/gerrors/zitadel_errors.go", "line": 57 }, "msg": "User could not be found (COMMAND-bd4ir1mblj)", "domain": { "instance_host": "localhost:8080", "protocol": "http" }, "protocol": "connect", "service": "zitadel.user.v2.UserService", "http_method": "POST", "path": "/zitadel.user.v2.UserService/DeleteUser", "request_id": "d5keo4oednc6klmo3vbg", "duration": 54229055, "TraceID": "daa21183bd8da5da56969b0ea45f4388", "SpanID": "70da728e3086c104", "err": { "kind": "NotFound", "message": "User could not be found", "id": "COMMAND-bd4ir1mblj", "reportLocation": { "filePath": "/home/tim/Repositories/zitadel/zitadel/internal/command/user_v2.go", "lineNumber": 140, "functionName": "github.com/zitadel/zitadel/internal/command.(*Commands).RemoveUserV2" } } } ``` </details> When stack trace is enabled, using a standard logger, it is logged under the `stackTrace` key. <details> <summary>Standard JSON format with stack trace</summary> ```json { "time": "2026-01-15T14:36:56.522692368+01:00", "level": "WARN", "source": { "function": "github.com/zitadel/zitadel/internal/api/grpc/gerrors.ZITADELToConnectError", "file": "/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/gerrors/zitadel_errors.go", "line": 57 }, "msg": "User could not be found (COMMAND-bd4ir1mblj)", "domain": { "instance_host": "localhost:8080", "protocol": "http" }, "protocol": "connect", "service": "zitadel.user.v2.UserService", "http_method": "POST", "path": "/zitadel.user.v2.UserService/DeleteUser", "request_id": "d5kequ0ednc7lphvgfn0", "duration": 57820793, "TraceID": "1b05d2b01306f2a64feb1a24b65a89c5", "SpanID": "c5ff35a27a917764", "err": { "kind": "NotFound", "message": "User could not be found", "id": "COMMAND-bd4ir1mblj", "reportLocation": { "filePath": "/home/tim/Repositories/zitadel/zitadel/internal/command/user_v2.go", "lineNumber": 140, "functionName": "github.com/zitadel/zitadel/internal/command.(*Commands).RemoveUserV2" }, "stackTrace": "goroutine 673 [running]:\nruntime/debug.Stack()\n\t/usr/lib/go/src/runtime/debug/stack.go:26 +0x8e\ngithub.com/zitadel/zitadel/internal/zerrors.newZitadelError(0x5, {0x0, 0x0}, {0x6af7719, 0x12}, {0x6aff994, 0x14})\n\t/home/tim/Repositories/zitadel/zitadel/internal/zerrors/zerror.go:139 +0x1df\ngithub.com/zitadel/zitadel/internal/zerrors.ThrowNotFound({0x0, 0x0}, {0x6af7719, 0x12}, {0x6aff994, 0x14})\n\t/home/tim/Repositories/zitadel/zitadel/internal/zerrors/not_found.go:6 +0x70\ngithub.com/zitadel/zitadel/internal/command.(*Commands).RemoveUserV2(0xc0035a4008, {0xa3c1410, 0xc003ded5c0}, {0xc0029693a0, 0x3}, {0x0, 0x0}, {0xd550a60, 0x0, 0x0}, ...)\n\t/home/tim/Repositories/zitadel/zitadel/internal/command/user_v2.go:140 +0x711\ngithub.com/zitadel/zitadel/internal/api/grpc/user/v2.(*Server).DeleteUser(0xc00272d080, {0xa3c1410, 0xc003ded5c0}, 0xc002ebf180)\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/user/v2/user.go:191 +0x3f1\nconnectrpc.com/connect.NewUnaryHandler[...].func1({0xa405e70, 0xc002ebf180})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/handler.go:51 +0x17e\nconnectrpc.com/connect.unaryThunk.func1({0xa3c1410, 0xc003ded5c0}, {0xa405e70, 0xc002ebf180})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/interceptor.go:120 +0xd9\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.ActivityInterceptor.func1.1({0xa3c1410, 0xc003ded5c0}, {0xa405e70, 0xc002ebf180})\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/activity_interceptor.go:20 +0x1b1\nconnectrpc.com/connect.unaryThunk.func1({0xa3c1410, 0xc003ded4d0}, {0xa405e70, 0xc002ebf180})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/interceptor.go:120 +0xd9\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.ServiceHandler.func1.1({0xa3c1410, 0xc003ded4d0}, {0xa405e70, 0xc002ebf180})\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/service_interceptor.go:22 +0x283\nconnectrpc.com/connect.unaryThunk.func1({0xa3c1410, 0xc003ded4d0}, {0xa405e70, 0xc002ebf180})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/interceptor.go:120 +0xd9\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.validate({0xa3c1410, 0xc003ded4d0}, {0xa405e70, 0xc002ebf180}, 0xc0031bff60)\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/validation_interceptor.go:35 +0x1bd\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.ValidationHandler.func1.1({0xa3c1410, 0xc003ded4d0}, {0xa405e70, 0xc002ebf180})\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/validation_interceptor.go:15 +0x85\nconnectrpc.com/connect.unaryThunk.func1({0xa3c1410, 0xc003ded4d0}, {0xa405e70, 0xc002ebf180})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/interceptor.go:120 +0xd9\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.ExecutionHandler.func1.1({0xa3c1410, 0xc003ded4d0}, {0xa405e70, 0xc002ebf180})\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/execution_interceptor.go:39 +0x376\nconnectrpc.com/connect.unaryThunk.func1({0xa3c1410, 0xc003ded4d0}, {0xa405e70, 0xc002ebf180})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/interceptor.go:120 +0xd9\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.QuotaExhaustedInterceptor.func1.1({0xa3c1410, 0xc003ded4d0}, {0xa405e70, 0xc002ebf180})\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/quota_interceptor.go:25 +0x42d\nconnectrpc.com/connect.unaryThunk.func1({0xa3c1410, 0xc003ded4d0}, {0xa405e70, 0xc002ebf180})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/interceptor.go:120 +0xd9\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.TranslationHandler.func1.1({0xa3c1410, 0xc003ded4d0}, {0xa405e70, 0xc002ebf180})\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/translation_interceptor.go:18 +0x94\nconnectrpc.com/connect.unaryThunk.func1({0xa3c1410, 0xc003ded4d0}, {0xa405e70, 0xc002ebf180})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/interceptor.go:120 +0xd9\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.authorize({0xa3c1410, 0xc003dec000}, {0xa405e70, 0xc002ebf180}, 0xc00321c040, {0xa3f0a70, 0xc0004ebc70}, {{0xc000d2fc80, 0x9, 0x9}}, ...)\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/auth_interceptor.go:42 +0x894\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.AuthorizationInterceptor.func1.1({0xa3c1410, 0xc003dec000}, {0xa405e70, 0xc002ebf180})\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/auth_interceptor.go:17 +0x151\nconnectrpc.com/connect.unaryThunk.func1({0xa3c1410, 0xc003dec000}, {0xa405e70, 0xc002ebf180})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/interceptor.go:120 +0xd9\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.LimitsInterceptor.func1.1({0xa3c1410, 0xc003dec000}, {0xa405e70, 0xc002ebf180})\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/limits_interceptor.go:31 +0x3b7\nconnectrpc.com/connect.unaryThunk.func1({0xa3c1410, 0xc003dec000}, {0xa405e70, 0xc002ebf180})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/interceptor.go:120 +0xd9\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.toConnectError({0xa3c1410, 0xc003dec000}, {0xa405e70, 0xc002ebf180}, 0xc00321c060)\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/error_interceptor.go:21 +0x8a\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.ErrorHandler.func1.1({0xa3c1410, 0xc003dec000}, {0xa405e70, 0xc002ebf180})\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/error_interceptor.go:15 +0x85\nconnectrpc.com/connect.unaryThunk.func1({0xa3c1410, 0xc003dec000}, {0xa405e70, 0xc002ebf180})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/interceptor.go:120 +0xd9\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.AccessStorageInterceptor.func1.1({0xa3c1410, 0xc003dec000}, {0xa405e70, 0xc002ebf180})\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/access_interceptor.go:21 +0x814\nconnectrpc.com/connect.unaryThunk.func1({0xa3c1410, 0xc003dec000}, {0xa405e70, 0xc002ebf180})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/interceptor.go:120 +0xd9\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.addInstanceByRequestedHost({0xa3c1410, 0xc003db1110}, {0xa405e70, 0xc002ebf180}, 0xc00321c090, {0xa3a63e8, 0xc0002f4a00}, 0xc002dee9b0, {0xc0012106a0, 0x9})\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/instance_interceptor.go:107 +0xbc3\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.setInstance({0xa3c1410, 0xc003db10e0}, {0xa405e70, 0xc002ebf180}, 0xc00321c090, {0xa3a63e8, 0xc0002f4a00}, {0xc0012106a0, 0x9}, 0xc002dee9b0, ...)\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/instance_interceptor.go:61 +0xb05\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.InstanceInterceptor.func1.1({0xa3c1410, 0xc003db10e0}, {0xa405e70, 0xc002ebf180})\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/instance_interceptor.go:23 +0x147\nconnectrpc.com/connect.unaryThunk.func1({0xa3c1410, 0xc003db10e0}, {0xa405e70, 0xc002ebf180})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/interceptor.go:120 +0xd9\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.NoCacheInterceptor.func1.1({0xa3c1410, 0xc003db10e0}, {0xa405e70, 0xc002ebf180})\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/cache_interceptor.go:21 +0x351\nconnectrpc.com/connect.unaryThunk.func1({0xa3c1410, 0xc003db10e0}, {0xa405e70, 0xc002ebf180})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/interceptor.go:120 +0xd9\ngithub.com/zitadel/zitadel/backend/v3/instrumentation/logging.NewConnectInterceptor.func1({0xa3c1410, 0xc003db10e0}, {0xa405e70, 0xc002ebf180})\n\t/home/tim/Repositories/zitadel/zitadel/backend/v3/instrumentation/logging/connect_handler.go:26 +0x27b\nconnectrpc.com/connect.unaryThunk.func1({0xa3c1410, 0xc003db1050}, {0xa405e70, 0xc002ebf180})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/interceptor.go:120 +0xd9\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.RegisterMetrics({0xa3c1410, 0xc003db1050}, {0xa405e70, 0xc002ebf180}, 0xc00321c0d0, {0xcf754f8, 0x3, 0x3}, {0xc0008b8840, 0x4, ...})\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/metrics_interceptor.go:47 +0x3f3\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.MetricsHandler.func1.1({0xa3c1410, 0xc003db1050}, {0xa405e70, 0xc002ebf180})\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/metrics_interceptor.go:31 +0x111\nconnectrpc.com/connect.unaryThunk.func1({0xa3c1410, 0xc003db1050}, {0xa405e70, 0xc002ebf180})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/interceptor.go:120 +0xd9\nconnectrpc.com/otelconnect.(*Interceptor).WrapUnary.func1({0xa3c1410, 0xc003db1050}, {0xa405e70, 0xc002ebf180})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected]/interceptor.go:145 +0x16d7\nconnectrpc.com/connect.unaryThunk.func1({0xa3c1410, 0xc003db0ff0}, {0xa405e70, 0xc002ebf180})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/interceptor.go:120 +0xd9\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.CallDurationHandler.func1.1({0xa3c1410, 0xc003db0ff0}, {0xa405e70, 0xc002ebf180})\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/call_interceptor.go:15 +0xab\nconnectrpc.com/connect.NewUnaryHandler[...].func2({0x7f11083fe700, 0xc000503a40})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/handler.go:78 +0x4e2\nconnectrpc.com/connect.(*Handler).ServeHTTP(0xc0004dd420, {0xa3bb420, 0xc0006b22a0}, 0xc002e3b2c0)\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/handler.go:333 +0x870\ngithub.com/zitadel/zitadel/pkg/grpc/user/v2/userconnect.NewUserServiceHandler.func1({0xa3bb420, 0xc0006b22a0}, 0xc002e3b2c0)\n\t/home/tim/Repositories/zitadel/zitadel/pkg/grpc/user/v2/userconnect/user_service.connect.go:1951 +0x8e5\nnet/http.HandlerFunc.ServeHTTP(0xc002d7a000, {0xa3bb420, 0xc0006b22a0}, 0xc002e3b2c0)\n\t/usr/lib/go/src/net/http/server.go:2322 +0x33\ngithub.com/rs/cors.(*Cors).Handler.func1({0xa3bb420, 0xc0006b22a0}, 0xc002e3b2c0)\n\t/home/tim/go/pkg/mod/github.com/rs/[email protected].1/cors.go:289 +0x2fd\nnet/http.HandlerFunc.ServeHTTP(0xc000191aa0, {0xa3bb420, 0xc0006b22a0}, 0xc002e3b2c0)\n\t/usr/lib/go/src/net/http/server.go:2322 +0x33\ngithub.com/zitadel/zitadel/internal/api/http/middleware.RobotsTagHandler.func1({0xa3bb420, 0xc0006b22a0}, 0xc002e3b2c0)\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/http/middleware/robots_tag_interceptor.go:12 +0x88\nnet/http.HandlerFunc.ServeHTTP(0xc002e14798, {0xa3bb420, 0xc0006b22a0}, 0xc002e3b2c0)\n\t/usr/lib/go/src/net/http/server.go:2322 +0x33\ngithub.com/zitadel/zitadel/internal/api/http/middleware.WithOrigin.func1.1({0xa3bb420, 0xc0006b22a0}, 0xc002e3b180)\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/http/middleware/origin_interceptor.go:27 +0x42a\nnet/http.HandlerFunc.ServeHTTP(0xc003d89a40, {0xa3bb420, 0xc0006b22a0}, 0xc002e3b180)\n\t/usr/lib/go/src/net/http/server.go:2322 +0x33\ngithub.com/gorilla/mux.(*Router).ServeHTTP(0xc002dfa840, {0xa3bb420, 0xc0006b22a0}, 0xc002e3b180)\n\t/home/tim/go/pkg/mod/github.com/gorilla/[email protected]/mux.go:212 +0x2bb\ngolang.org/x/net/http2.(*serverConn).runHandler(0xc003e143c0, 0xc0006b22a0, 0xc0002f4000, 0xc002d9ae88)\n\t/home/tim/go/pkg/mod/golang.org/x/[email protected].0/http2/server.go:2424 +0x1b9\ncreated by golang.org/x/net/http2.(*serverConn).scheduleHandler in goroutine 607\n\t/home/tim/go/pkg/mod/golang.org/x/[email protected].0/http2/server.go:2359 +0x1df\n" } } ``` </details> When the GCP error reporting handler is enabled with stack trace, as per [formatting requirements](https://docs.cloud.google.com/error-reporting/docs/formatting-error-messages): - A `@type` field is provided - Some fields are renamed according to Google Cloud Logging specs. (severity, message etc) - The stack strace is appended to the `message` field. - The report location is moved into the top-level `reportLocation` field. The original error is still logged in the `error` field. This is not part of the error reporting API but may still be useful for logging. <details> <summary>GCP error report with stack trace</summary> ```json { "@type": "type.googleapis.com/google.devtools.clouderrorreporting.v1beta1.ReportedErrorEvent", "SpanID": "4fdeae9817ade4f5", "TraceID": "78e31366d4c36e0718a2dcb6ca73d2f3", "domain": { "instance_host": "localhost:8080", "protocol": "http" }, "duration": "82.464896ms", "error": { "id": "COMMAND-bd4ir1mblj", "kind": "NotFound", "message": "User could not be found" }, "http_method": "POST", "logging.googleapis.com/sourceLocation": { "function": "github.com/zitadel/zitadel/internal/api/grpc/gerrors.ZITADELToConnectError", "file": "/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/gerrors/zitadel_errors.go", "line": 57 }, "message": "ID=COMMAND-bd4ir1mblj Message=User could not be found\ngoroutine 1165 [running]:\nruntime/debug.Stack()\n\t/usr/lib/go/src/runtime/debug/stack.go:26 +0x8e\ngithub.com/zitadel/zitadel/internal/zerrors.newZitadelError(0x5, {0x0, 0x0}, {0x6af7719, 0x12}, {0x6aff994, 0x14})\n\t/home/tim/Repositories/zitadel/zitadel/internal/zerrors/zerror.go:139 +0x1df\ngithub.com/zitadel/zitadel/internal/zerrors.ThrowNotFound({0x0, 0x0}, {0x6af7719, 0x12}, {0x6aff994, 0x14})\n\t/home/tim/Repositories/zitadel/zitadel/internal/zerrors/not_found.go:6 +0x70\ngithub.com/zitadel/zitadel/internal/command.(*Commands).RemoveUserV2(0xc000498588, {0xa3c1450, 0xc002cba540}, {0xc003030910, 0xb}, {0x0, 0x0}, {0xd550a80, 0x0, 0x0}, ...)\n\t/home/tim/Repositories/zitadel/zitadel/internal/command/user_v2.go:140 +0x711\ngithub.com/zitadel/zitadel/internal/api/grpc/user/v2.(*Server).DeleteUser(0xc003101500, {0xa3c1450, 0xc002cba540}, 0xc00216ec80)\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/user/v2/user.go:191 +0x3f1\nconnectrpc.com/connect.NewUnaryHandler[...].func1({0xa405eb0, 0xc00216ec80})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/handler.go:51 +0x17e\nconnectrpc.com/connect.unaryThunk.func1({0xa3c1450, 0xc002cba540}, {0xa405eb0, 0xc00216ec80})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/interceptor.go:120 +0xd9\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.ActivityInterceptor.func1.1({0xa3c1450, 0xc002cba540}, {0xa405eb0, 0xc00216ec80})\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/activity_interceptor.go:20 +0x1b1\nconnectrpc.com/connect.unaryThunk.func1({0xa3c1450, 0xc002cba330}, {0xa405eb0, 0xc00216ec80})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/interceptor.go:120 +0xd9\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.ServiceHandler.func1.1({0xa3c1450, 0xc002cba330}, {0xa405eb0, 0xc00216ec80})\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/service_interceptor.go:22 +0x283\nconnectrpc.com/connect.unaryThunk.func1({0xa3c1450, 0xc002cba330}, {0xa405eb0, 0xc00216ec80})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/interceptor.go:120 +0xd9\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.validate({0xa3c1450, 0xc002cba330}, {0xa405eb0, 0xc00216ec80}, 0xc003033180)\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/validation_interceptor.go:35 +0x1bd\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.ValidationHandler.func1.1({0xa3c1450, 0xc002cba330}, {0xa405eb0, 0xc00216ec80})\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/validation_interceptor.go:15 +0x85\nconnectrpc.com/connect.unaryThunk.func1({0xa3c1450, 0xc002cba330}, {0xa405eb0, 0xc00216ec80})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/interceptor.go:120 +0xd9\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.ExecutionHandler.func1.1({0xa3c1450, 0xc002cba330}, {0xa405eb0, 0xc00216ec80})\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/execution_interceptor.go:39 +0x376\nconnectrpc.com/connect.unaryThunk.func1({0xa3c1450, 0xc002cba330}, {0xa405eb0, 0xc00216ec80})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/interceptor.go:120 +0xd9\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.QuotaExhaustedInterceptor.func1.1({0xa3c1450, 0xc002cba330}, {0xa405eb0, 0xc00216ec80})\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/quota_interceptor.go:25 +0x42d\nconnectrpc.com/connect.unaryThunk.func1({0xa3c1450, 0xc002cba330}, {0xa405eb0, 0xc00216ec80})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/interceptor.go:120 +0xd9\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.TranslationHandler.func1.1({0xa3c1450, 0xc002cba330}, {0xa405eb0, 0xc00216ec80})\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/translation_interceptor.go:18 +0x94\nconnectrpc.com/connect.unaryThunk.func1({0xa3c1450, 0xc002cba330}, {0xa405eb0, 0xc00216ec80})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/interceptor.go:120 +0xd9\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.authorize({0xa3c1450, 0xc0015827b0}, {0xa405eb0, 0xc00216ec80}, 0xc0030331e0, {0xa3f0ab0, 0xc000203f10}, {{0xc000865980, 0x9, 0x9}}, ...)\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/auth_interceptor.go:42 +0x894\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.AuthorizationInterceptor.func1.1({0xa3c1450, 0xc0015827b0}, {0xa405eb0, 0xc00216ec80})\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/auth_interceptor.go:17 +0x151\nconnectrpc.com/connect.unaryThunk.func1({0xa3c1450, 0xc0015827b0}, {0xa405eb0, 0xc00216ec80})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/interceptor.go:120 +0xd9\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.LimitsInterceptor.func1.1({0xa3c1450, 0xc0015827b0}, {0xa405eb0, 0xc00216ec80})\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/limits_interceptor.go:31 +0x3b7\nconnectrpc.com/connect.unaryThunk.func1({0xa3c1450, 0xc0015827b0}, {0xa405eb0, 0xc00216ec80})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/interceptor.go:120 +0xd9\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.toConnectError({0xa3c1450, 0xc0015827b0}, {0xa405eb0, 0xc00216ec80}, 0xc003033200)\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/error_interceptor.go:21 +0x8a\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.ErrorHandler.func1.1({0xa3c1450, 0xc0015827b0}, {0xa405eb0, 0xc00216ec80})\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/error_interceptor.go:15 +0x85\nconnectrpc.com/connect.unaryThunk.func1({0xa3c1450, 0xc0015827b0}, {0xa405eb0, 0xc00216ec80})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/interceptor.go:120 +0xd9\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.AccessStorageInterceptor.func1.1({0xa3c1450, 0xc0015827b0}, {0xa405eb0, 0xc00216ec80})\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/access_interceptor.go:21 +0x814\nconnectrpc.com/connect.unaryThunk.func1({0xa3c1450, 0xc0015827b0}, {0xa405eb0, 0xc00216ec80})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/interceptor.go:120 +0xd9\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.addInstanceByRequestedHost({0xa3c1450, 0xc001636630}, {0xa405eb0, 0xc00216ec80}, 0xc003033230, {0xa3a6428, 0xc00069f040}, 0xc002076910, {0xc002cef510, 0x9})\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/instance_interceptor.go:107 +0xbc3\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.setInstance({0xa3c1450, 0xc0016365a0}, {0xa405eb0, 0xc00216ec80}, 0xc003033230, {0xa3a6428, 0xc00069f040}, {0xc002cef510, 0x9}, 0xc002076910, ...)\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/instance_interceptor.go:61 +0xb05\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.InstanceInterceptor.func1.1({0xa3c1450, 0xc0016365a0}, {0xa405eb0, 0xc00216ec80})\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/instance_interceptor.go:23 +0x147\nconnectrpc.com/connect.unaryThunk.func1({0xa3c1450, 0xc0016365a0}, {0xa405eb0, 0xc00216ec80})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/interceptor.go:120 +0xd9\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.NoCacheInterceptor.func1.1({0xa3c1450, 0xc0016365a0}, {0xa405eb0, 0xc00216ec80})\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/cache_interceptor.go:21 +0x351\nconnectrpc.com/connect.unaryThunk.func1({0xa3c1450, 0xc0016365a0}, {0xa405eb0, 0xc00216ec80})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/interceptor.go:120 +0xd9\ngithub.com/zitadel/zitadel/backend/v3/instrumentation/logging.NewConnectInterceptor.func1({0xa3c1450, 0xc0016365a0}, {0xa405eb0, 0xc00216ec80})\n\t/home/tim/Repositories/zitadel/zitadel/backend/v3/instrumentation/logging/connect_handler.go:26 +0x27b\nconnectrpc.com/connect.unaryThunk.func1({0xa3c1450, 0xc0016364e0}, {0xa405eb0, 0xc00216ec80})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/interceptor.go:120 +0xd9\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.RegisterMetrics({0xa3c1450, 0xc0016364e0}, {0xa405eb0, 0xc00216ec80}, 0xc003033270, {0xcf754f8, 0x3, 0x3}, {0xc000530f60, 0x4, ...})\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/metrics_interceptor.go:47 +0x3f3\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.MetricsHandler.func1.1({0xa3c1450, 0xc0016364e0}, {0xa405eb0, 0xc00216ec80})\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/metrics_interceptor.go:31 +0x111\nconnectrpc.com/connect.unaryThunk.func1({0xa3c1450, 0xc0016364e0}, {0xa405eb0, 0xc00216ec80})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/interceptor.go:120 +0xd9\nconnectrpc.com/otelconnect.(*Interceptor).WrapUnary.func1({0xa3c1450, 0xc0016364e0}, {0xa405eb0, 0xc00216ec80})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected]/interceptor.go:145 +0x16d7\nconnectrpc.com/connect.unaryThunk.func1({0xa3c1450, 0xc001636450}, {0xa405eb0, 0xc00216ec80})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/interceptor.go:120 +0xd9\ngithub.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.CallDurationHandler.func1.1({0xa3c1450, 0xc001636450}, {0xa405eb0, 0xc00216ec80})\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/server/connect_middleware/call_interceptor.go:15 +0xab\nconnectrpc.com/connect.NewUnaryHandler[...].func2({0x7fb6100a8e60, 0xc002fa5680})\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/handler.go:78 +0x4e2\nconnectrpc.com/connect.(*Handler).ServeHTTP(0xc0040768c0, {0xa3bb460, 0xc0025bf360}, 0xc0024c23c0)\n\t/home/tim/go/pkg/mod/connectrpc.com/[email protected].1/handler.go:333 +0x870\ngithub.com/zitadel/zitadel/pkg/grpc/user/v2/userconnect.NewUserServiceHandler.func1({0xa3bb460, 0xc0025bf360}, 0xc0024c23c0)\n\t/home/tim/Repositories/zitadel/zitadel/pkg/grpc/user/v2/userconnect/user_service.connect.go:1951 +0x8e5\nnet/http.HandlerFunc.ServeHTTP(0xc00314a200, {0xa3bb460, 0xc0025bf360}, 0xc0024c23c0)\n\t/usr/lib/go/src/net/http/server.go:2322 +0x33\ngithub.com/rs/cors.(*Cors).Handler.func1({0xa3bb460, 0xc0025bf360}, 0xc0024c23c0)\n\t/home/tim/go/pkg/mod/github.com/rs/[email protected].1/cors.go:289 +0x2fd\nnet/http.HandlerFunc.ServeHTTP(0xc0022cbdc0, {0xa3bb460, 0xc0025bf360}, 0xc0024c23c0)\n\t/usr/lib/go/src/net/http/server.go:2322 +0x33\ngithub.com/zitadel/zitadel/internal/api/http/middleware.RobotsTagHandler.func1({0xa3bb460, 0xc0025bf360}, 0xc0024c23c0)\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/http/middleware/robots_tag_interceptor.go:12 +0x88\nnet/http.HandlerFunc.ServeHTTP(0xc001c8fae8, {0xa3bb460, 0xc0025bf360}, 0xc0024c23c0)\n\t/usr/lib/go/src/net/http/server.go:2322 +0x33\ngithub.com/zitadel/zitadel/internal/api/http/middleware.WithOrigin.func1.1({0xa3bb460, 0xc0025bf360}, 0xc0024c2140)\n\t/home/tim/Repositories/zitadel/zitadel/internal/api/http/middleware/origin_interceptor.go:27 +0x42a\nnet/http.HandlerFunc.ServeHTTP(0xc00048e540, {0xa3bb460, 0xc0025bf360}, 0xc0024c2140)\n\t/usr/lib/go/src/net/http/server.go:2322 +0x33\ngithub.com/gorilla/mux.(*Router).ServeHTTP(0xc00207c540, {0xa3bb460, 0xc0025bf360}, 0xc0024c2140)\n\t/home/tim/go/pkg/mod/github.com/gorilla/[email protected]/mux.go:212 +0x2bb\ngolang.org/x/net/http2.(*serverConn).runHandler(0xc0025545a0, 0xc0025bf360, 0xc0023fd180, 0xc001703488)\n\t/home/tim/go/pkg/mod/golang.org/x/[email protected].0/http2/server.go:2424 +0x1b9\ncreated by golang.org/x/net/http2.(*serverConn).scheduleHandler in goroutine 620\n\t/home/tim/go/pkg/mod/golang.org/x/[email protected].0/http2/server.go:2359 +0x1df\n", "path": "/zitadel.user.v2.UserService/DeleteUser", "protocol": "connect", "reportLocation": { "filePath": "/home/tim/Repositories/zitadel/zitadel/internal/command/user_v2.go", "lineNumber": 140, "functionName": "github.com/zitadel/zitadel/internal/command.(*Commands).RemoveUserV2" }, "request_id": "d5kgsu8edncaon6puvag", "service": "zitadel.user.v2.UserService", "severity": "WARNING", "time": "2026-01-15T16:57:45.341000592+01:00" } ``` </details> When the GCP error reporting handler is enabled without stack trace, the `message` field contains the error string, also as per formatting requirements. <details> <summary>GCP error report without stack trace</summary> ```json { "@type": "type.googleapis.com/google.devtools.clouderrorreporting.v1beta1.ReportedErrorEvent", "SpanID": "2b4157f874eddbf4", "TraceID": "c34f7404fc0d021e1868ccc687bf2995", "domain": { "instance_host": "localhost:8080", "protocol": "http" }, "duration": "52.061597ms", "error": { "id": "COMMAND-bd4ir1mblj", "kind": "NotFound", "message": "User could not be found" }, "http_method": "POST", "logging.googleapis.com/sourceLocation": { "function": "github.com/zitadel/zitadel/internal/api/grpc/gerrors.ZITADELToConnectError", "file": "/home/tim/Repositories/zitadel/zitadel/internal/api/grpc/gerrors/zitadel_errors.go", "line": 57 }, "message": "ID=COMMAND-bd4ir1mblj Message=User could not be found", "path": "/zitadel.user.v2.UserService/DeleteUser", "protocol": "connect", "reportLocation": { "filePath": "/home/tim/Repositories/zitadel/zitadel/internal/command/user_v2.go", "lineNumber": 140, "functionName": "github.com/zitadel/zitadel/internal/command.(*Commands).RemoveUserV2" }, "request_id": "d5kf5tgedncaiajvls9g", "service": "zitadel.user.v2.UserService", "severity": "WARNING", "time": "2026-01-15T15:00:22.910899273+01:00" } ``` </details> # Additional Changes - Private constructor for zitadel errors so that the stack-depth is consistent for different ways of error creation. (Throw and Create variants) - Request logs are now always at info level. - Bump Go version 1.25, required for `sloggcp` # Additional Context - closes #11329 --------- Co-authored-by: Copilot <[email protected]>
This commit is contained in:
@@ -33,7 +33,7 @@
|
||||
},
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/go:1": {
|
||||
"version": "1.24"
|
||||
"version": "1.25"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/docker-outside-of-docker": {},
|
||||
"ghcr.io/schlich/devcontainer-features/cypress:1": {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by "enumer -type=LogFormat -trimprefix=LogFormat -text -linecomment"; DO NOT EDIT.
|
||||
// Code generated by "enumer -type=LogFormat -trimprefix=LogFormat -text -linecomment -transform=snake"; DO NOT EDIT.
|
||||
|
||||
package instrumentation
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const _LogFormatName = "DisabledTextJSONGCP"
|
||||
const _LogFormatName = "disabledtextjsongcpgcp_error_reporting"
|
||||
|
||||
var _LogFormatIndex = [...]uint8{0, 0, 8, 12, 16, 19}
|
||||
var _LogFormatIndex = [...]uint8{0, 0, 8, 12, 16, 19, 38}
|
||||
|
||||
const _LogFormatLowerName = "disabledtextjsongcp"
|
||||
const _LogFormatLowerName = "disabledtextjsongcpgcp_error_reporting"
|
||||
|
||||
func (i LogFormat) String() string {
|
||||
if i < 0 || i >= LogFormat(len(_LogFormatIndex)-1) {
|
||||
@@ -29,9 +29,10 @@ func _LogFormatNoOp() {
|
||||
_ = x[LogFormatText-(2)]
|
||||
_ = x[LogFormatJSON-(3)]
|
||||
_ = x[LogFormatGCP-(4)]
|
||||
_ = x[LogFormatGCPErrorReporting-(5)]
|
||||
}
|
||||
|
||||
var _LogFormatValues = []LogFormat{LogFormatUndefined, LogFormatDisabled, LogFormatText, LogFormatJSON, LogFormatGCP}
|
||||
var _LogFormatValues = []LogFormat{LogFormatUndefined, LogFormatDisabled, LogFormatText, LogFormatJSON, LogFormatGCP, LogFormatGCPErrorReporting}
|
||||
|
||||
var _LogFormatNameToValueMap = map[string]LogFormat{
|
||||
_LogFormatName[0:0]: LogFormatUndefined,
|
||||
@@ -44,6 +45,8 @@ var _LogFormatNameToValueMap = map[string]LogFormat{
|
||||
_LogFormatLowerName[12:16]: LogFormatJSON,
|
||||
_LogFormatName[16:19]: LogFormatGCP,
|
||||
_LogFormatLowerName[16:19]: LogFormatGCP,
|
||||
_LogFormatName[19:38]: LogFormatGCPErrorReporting,
|
||||
_LogFormatLowerName[19:38]: LogFormatGCPErrorReporting,
|
||||
}
|
||||
|
||||
var _LogFormatNames = []string{
|
||||
@@ -52,6 +55,7 @@ var _LogFormatNames = []string{
|
||||
_LogFormatName[8:12],
|
||||
_LogFormatName[12:16],
|
||||
_LogFormatName[16:19],
|
||||
_LogFormatName[19:38],
|
||||
}
|
||||
|
||||
// LogFormatString retrieves an enum value from the enum constants string name.
|
||||
|
||||
@@ -2,7 +2,6 @@ package logging
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
@@ -29,43 +28,7 @@ func NewConnectInterceptor(next connect.UnaryFunc, ignoredMethodSuffixes ...stri
|
||||
if err != nil {
|
||||
code = connect.CodeOf(err)
|
||||
}
|
||||
logger.Log(ctx,
|
||||
assertConnectLevel(code),
|
||||
"connect RPC served",
|
||||
"code", code,
|
||||
)
|
||||
|
||||
logger.InfoContext(ctx, "connect RPC request", "code", code)
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
func assertConnectLevel(code connect.Code) slog.Level {
|
||||
switch code {
|
||||
case connect.CodeUnimplemented:
|
||||
return slog.LevelDebug
|
||||
|
||||
// client errors
|
||||
case connect.CodeCanceled,
|
||||
connect.CodeInvalidArgument,
|
||||
connect.CodeNotFound,
|
||||
connect.CodeAlreadyExists,
|
||||
connect.CodePermissionDenied,
|
||||
connect.CodeResourceExhausted,
|
||||
connect.CodeFailedPrecondition,
|
||||
connect.CodeOutOfRange,
|
||||
connect.CodeUnauthenticated:
|
||||
return slog.LevelWarn
|
||||
|
||||
// server errors
|
||||
case connect.CodeUnknown,
|
||||
connect.CodeAborted,
|
||||
connect.CodeDeadlineExceeded,
|
||||
connect.CodeInternal,
|
||||
connect.CodeUnavailable,
|
||||
connect.CodeDataLoss:
|
||||
return slog.LevelError
|
||||
|
||||
default: // includes 0 code when no error
|
||||
return slog.LevelInfo
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package logging
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
@@ -31,44 +30,7 @@ func NewGrpcInterceptor(ignoredMethodSuffixes ...string) grpc.UnaryServerInterce
|
||||
if err != nil {
|
||||
code = status.Code(err)
|
||||
}
|
||||
logger.Log(ctx,
|
||||
assertGrpcLevel(code),
|
||||
"gRPC served",
|
||||
"code", code,
|
||||
)
|
||||
|
||||
logger.InfoContext(ctx, "gRPC request", "code", code)
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
func assertGrpcLevel(code codes.Code) slog.Level {
|
||||
switch code {
|
||||
case codes.Unimplemented:
|
||||
return slog.LevelDebug
|
||||
|
||||
// client errors
|
||||
case codes.Canceled,
|
||||
codes.InvalidArgument,
|
||||
codes.NotFound,
|
||||
codes.AlreadyExists,
|
||||
codes.PermissionDenied,
|
||||
codes.ResourceExhausted,
|
||||
codes.FailedPrecondition,
|
||||
codes.OutOfRange,
|
||||
codes.Unauthenticated:
|
||||
return slog.LevelWarn
|
||||
|
||||
// server errors
|
||||
case codes.Unknown,
|
||||
codes.Aborted,
|
||||
codes.DeadlineExceeded,
|
||||
codes.Internal,
|
||||
codes.Unavailable,
|
||||
codes.DataLoss:
|
||||
return slog.LevelError
|
||||
case codes.OK:
|
||||
fallthrough
|
||||
default: // includes 0 code when no error
|
||||
return slog.LevelInfo
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package logging
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
slogctx "github.com/veqryn/slog-context"
|
||||
@@ -23,27 +22,10 @@ func NewHandler(next http.Handler, service string, ignoredPrefix ...string) http
|
||||
|
||||
next.ServeHTTP(sw, r.WithContext(ctx))
|
||||
|
||||
logger.Log(ctx,
|
||||
LogLevelFromStatus(sw.status),
|
||||
"http request served",
|
||||
"status", sw.status,
|
||||
)
|
||||
logger.InfoContext(ctx, "http request", "status", sw.status)
|
||||
})
|
||||
}
|
||||
|
||||
func LogLevelFromStatus(status int) slog.Level {
|
||||
if status < 200 {
|
||||
return slog.LevelDebug
|
||||
}
|
||||
if status < 400 {
|
||||
return slog.LevelInfo
|
||||
}
|
||||
if status < 500 {
|
||||
return slog.LevelWarn
|
||||
}
|
||||
return slog.LevelError
|
||||
}
|
||||
|
||||
// statusWriter is a [http.ResponseWriter] that captures the status code for logging.
|
||||
type statusWriter struct {
|
||||
http.ResponseWriter
|
||||
|
||||
@@ -9,21 +9,22 @@ import (
|
||||
"time"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"github.com/muhlemmer/sloggcp"
|
||||
"github.com/rs/xid"
|
||||
slogmulti "github.com/samber/slog-multi"
|
||||
slogctx "github.com/veqryn/slog-context"
|
||||
slogotel "github.com/veqryn/slog-context/otel"
|
||||
"github.com/zitadel/sloggcp"
|
||||
"go.opentelemetry.io/contrib/bridges/otelslog"
|
||||
"go.opentelemetry.io/otel/sdk/log"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
http_util "github.com/zitadel/zitadel/internal/api/http"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
type LogFormat int
|
||||
|
||||
//go:generate enumer -type=LogFormat -trimprefix=LogFormat -text -linecomment
|
||||
//go:generate enumer -type=LogFormat -trimprefix=LogFormat -text -linecomment -transform=snake
|
||||
const (
|
||||
// Empty line comment sets empty string of unspecified value
|
||||
LogFormatUndefined LogFormat = iota //
|
||||
@@ -32,19 +33,24 @@ const (
|
||||
LogFormatJSON
|
||||
// JSON formatted logs compatible with Google Cloud Platform
|
||||
LogFormatGCP
|
||||
LogFormatGCPErrorReporting
|
||||
)
|
||||
|
||||
type LogConfig struct {
|
||||
Level slog.Level
|
||||
Format LogFormat
|
||||
AddSource bool
|
||||
GoogleProjectID string
|
||||
GoogleLogName string
|
||||
Exporter ExporterConfig
|
||||
Level slog.Level
|
||||
Format LogFormat
|
||||
AddSource bool
|
||||
Errors ErrorConfig
|
||||
Exporter ExporterConfig
|
||||
}
|
||||
|
||||
type ErrorConfig struct {
|
||||
ReportLocation bool
|
||||
StackTrace bool
|
||||
}
|
||||
|
||||
// setLogger configures the global slog logger.
|
||||
// Logs are sent to StdErr and/or the [log.LoggerProvider].
|
||||
// Logs are sent to [os.Stderr] and/or the [log.LoggerProvider].
|
||||
//
|
||||
// When present in the context, each line emitted contains:
|
||||
// - Trace ID
|
||||
@@ -70,8 +76,15 @@ func setLogger(provider *log.LoggerProvider, cfg LogConfig) {
|
||||
case LogFormatGCP:
|
||||
options.ReplaceAttr = sloggcp.ReplaceAttr
|
||||
stdErrHandler = slog.NewJSONHandler(os.Stderr, options)
|
||||
case LogFormatGCPErrorReporting:
|
||||
zerrors.GCPErrorReportingEnabled(true)
|
||||
options.ReplaceAttr = replaceErrAttr
|
||||
stdErrHandler = sloggcp.NewErrorReportingHandler(os.Stderr, options)
|
||||
}
|
||||
|
||||
zerrors.EnableReportLocation(cfg.Errors.ReportLocation)
|
||||
zerrors.EnableStackTrace(cfg.Errors.StackTrace)
|
||||
|
||||
stdErrHandler = slogctx.NewHandler(
|
||||
stdErrHandler,
|
||||
&slogctx.HandlerOptions{
|
||||
@@ -195,3 +208,11 @@ func requestExtractor(ctx context.Context, _ time.Time, _ slog.Level, _ string)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// replaceErrAttr renames the "err" attribute to the Google Cloud Platform compatible error key.
|
||||
func replaceErrAttr(groups []string, a slog.Attr) slog.Attr {
|
||||
if len(groups) == 0 && a.Key == "err" {
|
||||
a.Key = sloggcp.ErrorKey
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
@@ -49,10 +49,17 @@ Instrumentation:
|
||||
# - "text": Logs are printed on StdErr as human-readable text
|
||||
# - "json": Logs are printed on StdErr as JSON objects
|
||||
# - "gcp": JSON formatted logs compatible with Google Cloud Platform logging. Need to configure GoogleProjectID below!
|
||||
# - "gcp_error_reporting": JSON formatted logs compatible with Google Cloud Platform Error Reporting. Need to configure GoogleProjectID below!
|
||||
Format: "text" # ZITADEL_INSTRUMENTATION_LOG_STDERR
|
||||
# Adds the source file and line number where the log line was emitted.
|
||||
# Not to be confused with the source of an error.
|
||||
AddSource: true # ZITADEL_INSTRUMENTATION_LOG_ADDSOURCE
|
||||
# Configure how errors are logged.
|
||||
Errors:
|
||||
# Adds the source file, line number and function where the error was created.
|
||||
ReportLocation: true # ZITADEL_INSTRUMENTATION_LOG_ERRORS_REPORTLOCATION
|
||||
# Adds stack traces to logged errors.
|
||||
StackTrace: false # ZITADEL_INSTRUMENTATION_LOG_ERRORS_STACKTRACE
|
||||
Exporter:
|
||||
# The following exporter types are supported:
|
||||
# - "none": Disables OTEL log exporter
|
||||
|
||||
@@ -49,10 +49,17 @@ Instrumentation:
|
||||
# - "text": Logs are printed on StdErr as human-readable text
|
||||
# - "json": Logs are printed on StdErr as JSON objects
|
||||
# - "gcp": JSON formatted logs compatible with Google Cloud Platform logging. Need to configure GoogleProjectID below!
|
||||
# - "gcp_error_reporting": JSON formatted logs compatible with Google Cloud Platform Error Reporting. Need to configure GoogleProjectID below!
|
||||
Format: "text" # ZITADEL_INSTRUMENTATION_LOG_STDERR
|
||||
# Adds the source file and line number where the log line was emitted.
|
||||
# Not to be confused with the source of an error.
|
||||
AddSource: true # ZITADEL_INSTRUMENTATION_LOG_ADDSOURCE
|
||||
# Configure how errors are logged.
|
||||
Errors:
|
||||
# Adds the source file, line number and function where the error was created.
|
||||
ReportLocation: true # ZITADEL_INSTRUMENTATION_LOG_ERRORS_REPORTLOCATION
|
||||
# Adds stack traces to logged errors.
|
||||
StackTrace: false # ZITADEL_INSTRUMENTATION_LOG_ERRORS_STACKTRACE
|
||||
Exporter:
|
||||
# The following exporter types are supported:
|
||||
# - "none": Disables OTEL log exporter
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
module github.com/zitadel/zitadel
|
||||
|
||||
go 1.24.0
|
||||
|
||||
toolchain go1.24.7
|
||||
go 1.25
|
||||
|
||||
require (
|
||||
cloud.google.com/go/profiler v0.4.3
|
||||
@@ -67,7 +65,6 @@ require (
|
||||
github.com/muesli/gamut v0.3.1
|
||||
github.com/muhlemmer/gu v0.3.1
|
||||
github.com/muhlemmer/httpforwarded v0.1.0
|
||||
github.com/muhlemmer/sloggcp v0.1.0
|
||||
github.com/nicksnyder/go-i18n/v2 v2.4.0
|
||||
github.com/pashagolub/pgxmock/v4 v4.9.0
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2
|
||||
@@ -104,6 +101,7 @@ require (
|
||||
github.com/zitadel/passwap v0.10.0
|
||||
github.com/zitadel/saml v0.4.1
|
||||
github.com/zitadel/schema v1.3.1
|
||||
github.com/zitadel/sloggcp v0.5.1
|
||||
go.opentelemetry.io/contrib/bridges/otelslog v0.13.0
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0
|
||||
|
||||
@@ -589,8 +589,6 @@ github.com/muhlemmer/gu v0.3.1 h1:7EAqmFrW7n3hETvuAdmFmn4hS8W+z3LgKtrnow+YzNM=
|
||||
github.com/muhlemmer/gu v0.3.1/go.mod h1:YHtHR+gxM+bKEIIs7Hmi9sPT3ZDUvTN/i88wQpZkrdM=
|
||||
github.com/muhlemmer/httpforwarded v0.1.0 h1:x4DLrzXdliq8mprgUMR0olDvHGkou5BJsK/vWUetyzY=
|
||||
github.com/muhlemmer/httpforwarded v0.1.0/go.mod h1:yo9czKedo2pdZhoXe+yDkGVbU0TJ0q9oQ90BVoDEtw0=
|
||||
github.com/muhlemmer/sloggcp v0.1.0 h1:iWaksY9nzkqlEecQPkfd7hbASqaIQPSoM7kldC2kAmc=
|
||||
github.com/muhlemmer/sloggcp v0.1.0/go.mod h1:2JKYrbP9Xc/azEDCIhvMrKP4/bxm6CqjdnMPS3OfpT0=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
@@ -836,6 +834,8 @@ github.com/zitadel/saml v0.4.1 h1:4PPOAvnUFcfwOFIAFZ3oGTMD+tKVeF96ySfGBf5j3IU=
|
||||
github.com/zitadel/saml v0.4.1/go.mod h1:ybs3e4tIWdYgSYBpuCsvf3T4FNDfbXYM+GPv5vIpHYk=
|
||||
github.com/zitadel/schema v1.3.1 h1:QT3kwiRIRXXLVAs6gCK/u044WmUVh6IlbLXUsn6yRQU=
|
||||
github.com/zitadel/schema v1.3.1/go.mod h1:071u7D2LQacy1HAN+YnMd/mx1qVE2isb0Mjeqg46xnU=
|
||||
github.com/zitadel/sloggcp v0.5.1 h1:AoUN9+6iRZCAy0P+iR7TOpvCO+lOpUzGslvzKSoAKKc=
|
||||
github.com/zitadel/sloggcp v0.5.1/go.mod h1:QNiYB+IkVRsx/NT/Z2rnBevp9D4F/5o8DAGrUO0BBoo=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
|
||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
|
||||
@@ -3,11 +3,11 @@ package zerrors
|
||||
import "fmt"
|
||||
|
||||
func ThrowAlreadyExists(parent error, id, message string) error {
|
||||
return CreateZitadelError(KindAlreadyExists, parent, id, message)
|
||||
return newZitadelError(KindAlreadyExists, parent, id, message)
|
||||
}
|
||||
|
||||
func ThrowAlreadyExistsf(parent error, id, format string, a ...any) error {
|
||||
return CreateZitadelError(KindAlreadyExists, parent, id, fmt.Sprintf(format, a...))
|
||||
return newZitadelError(KindAlreadyExists, parent, id, fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
func IsErrorAlreadyExists(err error) bool {
|
||||
|
||||
@@ -3,11 +3,11 @@ package zerrors
|
||||
import "fmt"
|
||||
|
||||
func ThrowDeadlineExceeded(parent error, id, message string) error {
|
||||
return CreateZitadelError(KindDeadlineExceeded, parent, id, message)
|
||||
return newZitadelError(KindDeadlineExceeded, parent, id, message)
|
||||
}
|
||||
|
||||
func ThrowDeadlineExceededf(parent error, id, format string, a ...any) error {
|
||||
return CreateZitadelError(KindDeadlineExceeded, parent, id, fmt.Sprintf(format, a...))
|
||||
return newZitadelError(KindDeadlineExceeded, parent, id, fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
func IsDeadlineExceeded(err error) bool {
|
||||
|
||||
@@ -3,11 +3,11 @@ package zerrors
|
||||
import "fmt"
|
||||
|
||||
func ThrowInternal(parent error, id, message string) error {
|
||||
return CreateZitadelError(KindInternal, parent, id, message)
|
||||
return newZitadelError(KindInternal, parent, id, message)
|
||||
}
|
||||
|
||||
func ThrowInternalf(parent error, id, format string, a ...any) error {
|
||||
return CreateZitadelError(KindInternal, parent, id, fmt.Sprintf(format, a...))
|
||||
return newZitadelError(KindInternal, parent, id, fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
func IsInternal(err error) bool {
|
||||
|
||||
@@ -3,11 +3,11 @@ package zerrors
|
||||
import "fmt"
|
||||
|
||||
func ThrowInvalidArgument(parent error, id, message string) error {
|
||||
return CreateZitadelError(KindInvalidArgument, parent, id, message)
|
||||
return newZitadelError(KindInvalidArgument, parent, id, message)
|
||||
}
|
||||
|
||||
func ThrowInvalidArgumentf(parent error, id, format string, a ...any) error {
|
||||
return CreateZitadelError(KindInvalidArgument, parent, id, fmt.Sprintf(format, a...))
|
||||
return newZitadelError(KindInvalidArgument, parent, id, fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
func IsErrorInvalidArgument(err error) bool {
|
||||
|
||||
@@ -3,11 +3,11 @@ package zerrors
|
||||
import "fmt"
|
||||
|
||||
func ThrowNotFound(parent error, id, message string) error {
|
||||
return CreateZitadelError(KindNotFound, parent, id, message)
|
||||
return newZitadelError(KindNotFound, parent, id, message)
|
||||
}
|
||||
|
||||
func ThrowNotFoundf(parent error, id, format string, a ...any) error {
|
||||
return CreateZitadelError(KindNotFound, parent, id, fmt.Sprintf(format, a...))
|
||||
return newZitadelError(KindNotFound, parent, id, fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
func IsNotFound(err error) bool {
|
||||
|
||||
@@ -3,11 +3,11 @@ package zerrors
|
||||
import "fmt"
|
||||
|
||||
func ThrowPermissionDenied(parent error, id, message string) error {
|
||||
return CreateZitadelError(KindPermissionDenied, parent, id, message)
|
||||
return newZitadelError(KindPermissionDenied, parent, id, message)
|
||||
}
|
||||
|
||||
func ThrowPermissionDeniedf(parent error, id, format string, a ...any) error {
|
||||
return CreateZitadelError(KindPermissionDenied, parent, id, fmt.Sprintf(format, a...))
|
||||
return newZitadelError(KindPermissionDenied, parent, id, fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
func IsPermissionDenied(err error) bool {
|
||||
|
||||
@@ -3,11 +3,11 @@ package zerrors
|
||||
import "fmt"
|
||||
|
||||
func ThrowPreconditionFailed(parent error, id, message string) error {
|
||||
return CreateZitadelError(KindPreconditionFailed, parent, id, message)
|
||||
return newZitadelError(KindPreconditionFailed, parent, id, message)
|
||||
}
|
||||
|
||||
func ThrowPreconditionFailedf(parent error, id, format string, a ...any) error {
|
||||
return CreateZitadelError(KindPreconditionFailed, parent, id, fmt.Sprintf(format, a...))
|
||||
return newZitadelError(KindPreconditionFailed, parent, id, fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
func IsPreconditionFailed(err error) bool {
|
||||
|
||||
@@ -3,11 +3,11 @@ package zerrors
|
||||
import "fmt"
|
||||
|
||||
func ThrowResourceExhausted(parent error, id, message string) error {
|
||||
return CreateZitadelError(KindResourceExhausted, parent, id, message)
|
||||
return newZitadelError(KindResourceExhausted, parent, id, message)
|
||||
}
|
||||
|
||||
func ThrowResourceExhaustedf(parent error, id, format string, a ...any) error {
|
||||
return CreateZitadelError(KindResourceExhausted, parent, id, fmt.Sprintf(format, a...))
|
||||
return newZitadelError(KindResourceExhausted, parent, id, fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
func IsResourceExhausted(err error) bool {
|
||||
|
||||
@@ -3,11 +3,11 @@ package zerrors
|
||||
import "fmt"
|
||||
|
||||
func ThrowUnauthenticated(parent error, id, message string) error {
|
||||
return CreateZitadelError(KindUnauthenticated, parent, id, message)
|
||||
return newZitadelError(KindUnauthenticated, parent, id, message)
|
||||
}
|
||||
|
||||
func ThrowUnauthenticatedf(parent error, id, format string, a ...any) error {
|
||||
return CreateZitadelError(KindUnauthenticated, parent, id, fmt.Sprintf(format, a...))
|
||||
return newZitadelError(KindUnauthenticated, parent, id, fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
func IsUnauthenticated(err error) bool {
|
||||
|
||||
@@ -3,11 +3,11 @@ package zerrors
|
||||
import "fmt"
|
||||
|
||||
func ThrowUnavailable(parent error, id, message string) error {
|
||||
return CreateZitadelError(KindUnavailable, parent, id, message)
|
||||
return newZitadelError(KindUnavailable, parent, id, message)
|
||||
}
|
||||
|
||||
func ThrowUnavailablef(parent error, id, format string, a ...any) error {
|
||||
return CreateZitadelError(KindUnavailable, parent, id, fmt.Sprintf(format, a...))
|
||||
return newZitadelError(KindUnavailable, parent, id, fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
func IsUnavailable(err error) bool {
|
||||
|
||||
@@ -3,11 +3,11 @@ package zerrors
|
||||
import "fmt"
|
||||
|
||||
func ThrowUnimplemented(parent error, id, message string) error {
|
||||
return CreateZitadelError(KindUnimplemented, parent, id, message)
|
||||
return newZitadelError(KindUnimplemented, parent, id, message)
|
||||
}
|
||||
|
||||
func ThrowUnimplementedf(parent error, id, format string, a ...any) error {
|
||||
return CreateZitadelError(KindUnimplemented, parent, id, fmt.Sprintf(format, a...))
|
||||
return newZitadelError(KindUnimplemented, parent, id, fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
func IsUnimplemented(err error) bool {
|
||||
|
||||
@@ -3,11 +3,11 @@ package zerrors
|
||||
import "fmt"
|
||||
|
||||
func ThrowUnknown(parent error, id, message string) error {
|
||||
return CreateZitadelError(KindUnknown, parent, id, message)
|
||||
return newZitadelError(KindUnknown, parent, id, message)
|
||||
}
|
||||
|
||||
func ThrowUnknownf(parent error, id, format string, a ...any) error {
|
||||
return CreateZitadelError(KindUnknown, parent, id, fmt.Sprintf(format, a...))
|
||||
return newZitadelError(KindUnknown, parent, id, fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
func IsUnknown(err error) bool {
|
||||
|
||||
@@ -4,6 +4,10 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"runtime/debug"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/zitadel/sloggcp"
|
||||
)
|
||||
|
||||
//go:generate enumer -type=Kind -trimprefix=Kind -json
|
||||
@@ -76,24 +80,66 @@ const (
|
||||
KindUnauthenticated Kind = 16
|
||||
)
|
||||
|
||||
// Because errors are created through singletons, config is global.
|
||||
var (
|
||||
enableReportLocation atomic.Bool
|
||||
enableStackTrace atomic.Bool
|
||||
gcpErrorReporting atomic.Bool
|
||||
)
|
||||
|
||||
// EnableReportLocation enables or disables report locations for created errors.
|
||||
func EnableReportLocation(enable bool) {
|
||||
enableReportLocation.Store(enable)
|
||||
}
|
||||
|
||||
// EnableStackTrace enables or disables stack traces for created errors.
|
||||
func EnableStackTrace(enable bool) {
|
||||
enableStackTrace.Store(enable)
|
||||
}
|
||||
|
||||
// GCPErrorReportingEnabled enables or disables special handling for GCP Error Reporting.
|
||||
// It must be enabled when using a sloggcp handler to avoid duplicate report locations and stack traces.
|
||||
func GCPErrorReportingEnabled(enable bool) {
|
||||
gcpErrorReporting.Store(enable)
|
||||
}
|
||||
|
||||
type ZitadelError struct {
|
||||
Kind Kind
|
||||
Parent error
|
||||
Message string
|
||||
ID string
|
||||
|
||||
// location where the error was created
|
||||
reportLocation *sloggcp.ReportLocation
|
||||
// stack trace at the point the error was created
|
||||
stackTrace []byte
|
||||
hasStackTrace bool
|
||||
}
|
||||
|
||||
func ThrowError(parent error, id, message string) error {
|
||||
return CreateZitadelError(KindUnknown, parent, id, message)
|
||||
return newZitadelError(KindUnknown, parent, id, message)
|
||||
}
|
||||
|
||||
func CreateZitadelError(kind Kind, parent error, id, message string) *ZitadelError {
|
||||
return &ZitadelError{
|
||||
return newZitadelError(kind, parent, id, message)
|
||||
}
|
||||
|
||||
func newZitadelError(kind Kind, parent error, id, message string) *ZitadelError {
|
||||
err := &ZitadelError{
|
||||
Kind: kind,
|
||||
Parent: parent,
|
||||
ID: id,
|
||||
Message: message,
|
||||
}
|
||||
if enableReportLocation.Load() {
|
||||
// skip 2: newZitadelError + Create / Throw function.
|
||||
err.reportLocation = sloggcp.NewReportLocation(2)
|
||||
}
|
||||
if enableStackTrace.Load() {
|
||||
err.stackTrace = debug.Stack()
|
||||
err.hasStackTrace = true
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (err *ZitadelError) Error() string {
|
||||
@@ -158,10 +204,41 @@ func AsZitadelError(err error) (*ZitadelError, bool) {
|
||||
var _ slog.LogValuer = (*ZitadelError)(nil)
|
||||
|
||||
func (err *ZitadelError) LogValue() slog.Value {
|
||||
return slog.GroupValue(
|
||||
attributes := make([]slog.Attr, 0, 6)
|
||||
attributes = append(attributes,
|
||||
slog.String("kind", err.Kind.String()),
|
||||
slog.String("message", err.Message),
|
||||
slog.String("id", err.ID),
|
||||
slog.Any("parent", err.Parent),
|
||||
)
|
||||
if err.Parent != nil {
|
||||
attributes = append(attributes, slog.Any("parent", err.Parent))
|
||||
}
|
||||
// if gcp error reporting is enabled, log the error as a group without
|
||||
// report location and stack trace, as those are handled by the handler.
|
||||
if gcpErrorReporting.Load() {
|
||||
return slog.GroupValue(attributes...)
|
||||
}
|
||||
|
||||
if err.reportLocation != nil {
|
||||
attributes = append(attributes, slog.Any("reportLocation", err.reportLocation))
|
||||
}
|
||||
if err.hasStackTrace {
|
||||
attributes = append(attributes, slog.String("stackTrace", string(err.stackTrace)))
|
||||
}
|
||||
return slog.GroupValue(attributes...)
|
||||
}
|
||||
|
||||
// ReportLocation implements [sloggcp.ReportLocationError].
|
||||
func (err *ZitadelError) ReportLocation() *sloggcp.ReportLocation {
|
||||
return err.reportLocation
|
||||
}
|
||||
|
||||
// StackTrace implements [sloggcp.StackTraceError].
|
||||
func (err *ZitadelError) StackTrace() (trace []byte, ok bool) {
|
||||
return err.stackTrace, err.hasStackTrace
|
||||
}
|
||||
|
||||
var (
|
||||
_ sloggcp.ReportLocationError = (*ZitadelError)(nil)
|
||||
_ sloggcp.StackTraceError = (*ZitadelError)(nil)
|
||||
)
|
||||
|
||||
@@ -1,27 +1,25 @@
|
||||
package zerrors_test
|
||||
package zerrors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func TestErrorMethod(t *testing.T) {
|
||||
err := zerrors.ThrowError(nil, "id", "msg")
|
||||
err := ThrowError(nil, "id", "msg")
|
||||
expected := "ID=id Message=msg"
|
||||
assert.Equal(t, expected, err.Error())
|
||||
|
||||
err = zerrors.ThrowError(err, "subID", "subMsg")
|
||||
err = ThrowError(err, "subID", "subMsg")
|
||||
subExptected := "ID=subID Message=subMsg Parent=(ID=id Message=msg)"
|
||||
assert.Equal(t, subExptected, err.Error())
|
||||
}
|
||||
|
||||
func TestZitadelError_Is(t *testing.T) {
|
||||
parent := errors.New("parent error")
|
||||
target := zerrors.CreateZitadelError(zerrors.KindAborted, parent, "id", "message")
|
||||
target := newZitadelError(KindAborted, parent, "id", "message")
|
||||
tests := []struct {
|
||||
name string // description of this test case
|
||||
err error
|
||||
@@ -34,27 +32,27 @@ func TestZitadelError_Is(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "different kind",
|
||||
err: zerrors.CreateZitadelError(zerrors.KindNotFound, parent, "id", "message"),
|
||||
err: newZitadelError(KindNotFound, parent, "id", "message"),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "different id",
|
||||
err: zerrors.CreateZitadelError(zerrors.KindAborted, parent, "otherID", "message"),
|
||||
err: newZitadelError(KindAborted, parent, "otherID", "message"),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "different message",
|
||||
err: zerrors.CreateZitadelError(zerrors.KindAborted, parent, "id", "other message"),
|
||||
err: newZitadelError(KindAborted, parent, "id", "other message"),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "different parent",
|
||||
err: zerrors.CreateZitadelError(zerrors.KindAborted, errors.New("other parent"), "id", "message"),
|
||||
err: newZitadelError(KindAborted, errors.New("other parent"), "id", "message"),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "same error",
|
||||
err: zerrors.CreateZitadelError(zerrors.KindAborted, parent, "id", "message"),
|
||||
err: newZitadelError(KindAborted, parent, "id", "message"),
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user