diff --git a/content/en/docs/languages/go/instrumentation.md b/content/en/docs/languages/go/instrumentation.md index 1b2e51471c36..aa5811b0b7be 100644 --- a/content/en/docs/languages/go/instrumentation.md +++ b/content/en/docs/languages/go/instrumentation.md @@ -5,7 +5,7 @@ aliases: - manual_instrumentation weight: 30 description: Manual instrumentation for OpenTelemetry Go -cSpell:ignore: fatalf otlptrace sdktrace sighup +cSpell:ignore: fatalf logr logrus otelslog otlplog otlploghttp sdktrace sighup --- {{% docs/languages/instrumentation-intro %}} @@ -37,7 +37,6 @@ import ( "log" "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/exporters/otlp/otlptrace" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.21.0" @@ -876,7 +875,161 @@ meterProvider := metric.NewMeterProvider( ## Logs -The logs API is currently unstable, documentation TBA. +Logs are distinct from metrics and traces in that **there is no user-facing +OpenTelemetry logs API**. Instead, there is tooling to bridge logs from existing +popular log packages (such as slog, logrus, zap, logr) into the OpenTelemetry +ecosystem. For rationale behind this design decision, see +[Logging specification](/docs/specs/otel/logs/). + +The two typical workflows discussed below each cater to different application +requirements. + +### Direct-to-Collector + +**Status**: [Experimental](/docs/specs/otel/document-status/) + +In the direct-to-Collector workflow, logs are emitted directly from an +application to a collector using a network protocol (e.g. OTLP). This workflow +is simple to set up as it doesn't require any additional log forwarding +components, and allows an application to easily emit structured logs that +conform to the [log data model][log data model]. However, the overhead required +for applications to queue and export logs to a network location may not be +suitable for all applications. + +To use this workflow: + +- Configure the OpenTelemetry [Log SDK](#logs-sdk) to export log records to + desired target destination (the [collector][opentelemetry collector] or + other). +- Use an appropriate [Log Bridge](#log-bridge). + +#### Logs SDK + +The logs SDK dictates how logs are processed when using the +[direct-to-Collector](#direct-to-collector) workflow. No log SDK is needed when +using the [log forwarding](#via-file-or-stdout) workflow. + +The typical log SDK configuration installs a batching log record processor with +an OTLP exporter. + +To enable [logs](/docs/concepts/signals/logs/) in your app, you'll need to have +an initialized [`LoggerProvider`](/docs/concepts/signals/logs/#logger-provider) +that will let you use a [Log Bridge](#log-bridge). + +If a `LoggerProvider` is not created, the OpenTelemetry APIs for logs will use a +no-op implementation and fail to generate data. Therefore, you have to modify +the source code to include the SDK initialization code using the following +packages: + +- [`go.opentelemetry.io/otel`][] +- [`go.opentelemetry.io/otel/sdk/log`][] +- [`go.opentelemetry.io/otel/sdk/resource`][] +- [`go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`][] + +Ensure you have the right Go modules installed: + +```sh +go get go.opentelemetry.io/otel \ + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp \ + go.opentelemetry.io/otel/sdk \ + go.opentelemetry.io/otel/sdk/log +``` + +Then initialize a logger provider: + +```go +package main + +import ( + "context" + "fmt" + + "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp" + "go.opentelemetry.io/otel/log/global" + "go.opentelemetry.io/otel/sdk/log" + "go.opentelemetry.io/otel/sdk/resource" + semconv "go.opentelemetry.io/otel/semconv/v1.21.0" +) + +func main() { + ctx := context.Background() + + // Create resource. + res, err := newResource() + if err != nil { + panic(err) + } + + // Create a logger provider. + // You can pass this instance directly when creating bridges. + loggerProvider, err := newLoggerProvider(ctx, res) + if err != nil { + panic(err) + } + + // Handle shutdown properly so nothing leaks. + defer func() { + if err := loggerProvider.Shutdown(ctx); err != nil { + fmt.Println(err) + } + }() + + // Register as global logger provider so that it can be accessed global.LoggerProvider. + // Most log bridges use the global logger provider as default. + // If the global logger provider is not set then a no-op implementation + // is used, which fails to generate data. + global.SetLoggerProvider(loggerProvider) +} + +func newResource() (*resource.Resource, error) { + return resource.Merge(resource.Default(), + resource.NewWithAttributes(semconv.SchemaURL, + semconv.ServiceName("my-service"), + semconv.ServiceVersion("0.1.0"), + )) +} + +func newLoggerProvider(ctx context.Context, res *resource.Resource) (*log.LoggerProvider, error) { + exporter, err := otlploghttp.New(ctx) + if err != nil { + return nil, err + } + processor := log.NewBatchProcessor(exporter) + provider := log.NewLoggerProvider( + log.WithResource(res), + log.WithProcessor(processor), + ) + return provider, nil +} +``` + +Now that a `LoggerProvider` is configured, you can use it to set up a +[Log Bridge](#log-bridge). + +#### Log Bridge + +A log bridge is a component that bridges logs from an existing log package into +the OpenTelemetry [Log SDK](#logs-sdk) using the [Logs Bridge +API][logs bridge API]. Log bridges are available for various popular Go log +packages: + +- [logrus bridge][otellogrus] +- [slog bridge][otelslog] + +The links above contain full usage and installation documentation. + +### Via file or stdout + +In the file or stdout workflow, logs are written to files or standout output. +Another component (e.g. FluentBit) is responsible for reading / tailing the +logs, parsing them to more structured format, and forwarding them a target, such +as the collector. This workflow may be preferable in situations where +application requirements do not permit additional overhead from +[direct-to-Collector](#direct-to-collector). However, it requires that all log +fields required down stream are encoded into the logs, and that the component +reading the logs parse the data into the [log data model][log data model]. The +installation and configuration of log forwarding components is outside the scope +of this document. ## Next Steps @@ -887,11 +1040,21 @@ telemetry backends. [opentelemetry specification]: /docs/specs/otel/ [trace semantic conventions]: /docs/specs/semconv/general/trace/ [instrumentation library]: ../libraries/ +[opentelemetry collector]: + https://github.com/open-telemetry/opentelemetry-collector +[logs bridge API]: /docs/specs/otel/logs/bridge-api +[log data model]: /docs/specs/otel/logs/data-model +[otellogrus]: https://pkg.go.dev/go.opentelemetry.io/contrib/bridges/otellogrus +[otelslog]: https://pkg.go.dev/go.opentelemetry.io/contrib/bridges/otelslog [`go.opentelemetry.io/otel`]: https://pkg.go.dev/go.opentelemetry.io/otel [`go.opentelemetry.io/otel/exporters/stdout/stdoutmetric`]: https://pkg.go.dev/go.opentelemetry.io/otel/exporters/stdout/stdoutmetric [`go.opentelemetry.io/otel/metric`]: https://pkg.go.dev/go.opentelemetry.io/otel/metric +[`go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`]: + https://pkg.go.dev/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp +[`go.opentelemetry.io/otel/sdk/log`]: + https://pkg.go.dev/go.opentelemetry.io/otel/sdk/log [`go.opentelemetry.io/otel/sdk/metric`]: https://pkg.go.dev/go.opentelemetry.io/otel/sdk/metric [`go.opentelemetry.io/otel/sdk/resource`]: diff --git a/content/en/docs/languages/go/resources.md b/content/en/docs/languages/go/resources.md index 4acbfc275868..dea97339c231 100644 --- a/content/en/docs/languages/go/resources.md +++ b/content/en/docs/languages/go/resources.md @@ -6,8 +6,8 @@ cSpell:ignore: sdktrace thirdparty {{% docs/languages/resources-intro %}} -Resources should be assigned to a tracer provider at its initialization, and are -created much like attributes: +Resources should be assigned to a tracer, meter, and logger provider at its +initialization, and are created much like attributes: ```go res := resource.NewWithAttributes( diff --git a/static/refcache.json b/static/refcache.json index 60c272e12fa3..549664b56a51 100644 --- a/static/refcache.json +++ b/static/refcache.json @@ -7007,6 +7007,14 @@ "StatusCode": 200, "LastSeen": "2024-03-01T16:49:37.76693+01:00" }, + "https://pkg.go.dev/go.opentelemetry.io/contrib/bridges/otellogrus": { + "StatusCode": 200, + "LastSeen": "2024-05-15T16:22:37.734630943Z" + }, + "https://pkg.go.dev/go.opentelemetry.io/contrib/bridges/otelslog": { + "StatusCode": 200, + "LastSeen": "2024-05-10T11:02:15.508410781Z" + }, "https://pkg.go.dev/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp": { "StatusCode": 200, "LastSeen": "2024-01-19T15:36:28.468246594Z" @@ -7071,6 +7079,10 @@ "StatusCode": 200, "LastSeen": "2024-01-30T15:25:42.951557-05:00" }, + "https://pkg.go.dev/go.opentelemetry.io/otel/sdk/log": { + "StatusCode": 200, + "LastSeen": "2024-05-10T11:02:08.878761042Z" + }, "https://pkg.go.dev/go.opentelemetry.io/otel/sdk/metric": { "StatusCode": 200, "LastSeen": "2024-01-30T15:25:12.503352-05:00"