bridge

package module
v0.0.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 17, 2023 License: Apache-2.0 Imports: 9 Imported by: 4

README

opentelemetry-zap-bridge

This module convert every log record written to zap logger in golang into OpenTelemetry SDK log record and export it directly from the application

Project Life Cycle

This project depends on the experimental opentelemetry-logs-go project, and is thus experimental as well. It is recommend to use with caution.

Target Audience

This project might give you value if you:

  • Maintain or operate golang applications
  • Use zap logger in your golang applications, directly or via a framework
  • Use OpenTelemetry in your system, or plan to use it in the future
  • Looking to simplify your log pipeline by removing log agents from deployments
  • Starting a new project from scratch and looking to tailor a solution to your needs

Installation

go get github.com/keyval-dev/opentelemetry-zap-bridge

Usage

This package provides a NewOtelZapCore function that returns a zapcore.Core instance, there is also a wrapper AttachToZapLogger that is used to attache to existing zap logger. You can choose to use it in one of the following ways:

With zap.Logger

If you have a zap.Logger instance, which you obtained with zap.NewProduction() / zap.New(...) etc, you can attach a new zapcore.Core to this logger using the AttachToZapLogger function:

import (
    "go.uber.org/zap"
	bridge "github.com/keyval-dev/opentelemetry-zap-bridge"
)
	logger, _ := zap.NewProduction()
	logger = bridge.AttachToZapLogger(logger)
With Kubernetes controller-runtime Package

If you use kubebuilder, it autogenerates a zap.Logger for you:

	ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))

You can convert this code to attach an otel sdk to it like this:

import (
    "sigs.k8s.io/controller-runtime/pkg/log/zap"
    "github.com/go-logr/zapr"
	bridge "github.com/keyval-dev/opentelemetry-zap-bridge"
)

func main() {
    // ...
	logger := zap.NewRaw(zap.UseFlagOptions(&opts))
	logger = bridge.AttachToZapLogger(logger)
	ctrl.SetLogger(zapr.NewLogger(logger))
    // ...
}
With zap.New

You can use the NewOtelZapCore function to create a new zapcore.Core instance and use it with zap.New to create just otel logger without console or file logging:

import (
    "go.uber.org/zap"
    bridge "github.com/keyval-dev/opentelemetry-zap-bridge"
)

func main() {
    // ...
    logger, _ := zap.New(bridge.NewOtelZapCore(otelServiceName))
    // ...
}

Notice that it is not recommended for development, as you will not see any logs in your console. In production, this might cause no logs to be written to the file, thus making kubectl logs useless. It will however, make your application more performant, as it will not encode and write to the file.

With other zapcore.Core

If you are advanced user and you can create your own zapcore.Core instance, you can use the NewOtelZapCore function to create a new otel sdk zapcore.Core instance and use it with zapcore.NewTee to combine it with your existing zapcore.Core instance:

import (
    "go.uber.org/zap/zapcore"
    bridge "github.com/keyval-dev/opentelemetry-zap-bridge"
)

func main() {
    // yourZapCore := ...
    otelZapLogger := NewOtelZapCore()
    combinedCore := zapcore.NewTee(core, yourZapCore)
    logger := zap.New(combinedCore)

Configuration

Currently the supported configuration method is via OpenTelemetry standard environment variables which you can find here and here for otlp exporter.

Specifically, I found the following environment variables useful:

  • OTEL_SERVICE_NAME - to add a service name to the logs records resource, which is must-have for them to be useful.
  • OTEL_SDK_DISABLED=true - to disable the OpenTelemetry SDK, which allows a one switch to turn off otel SDK if needed (for example, local development, or as a step in migration)
  • OTEL_EXPORTER_OTLP_PROTOCOL=grpc - the default otlp protocol is http, which is less performant than grpc. this is how you can change it.
  • OTEL_EXPORTER_OTLP_INSECURE=true - Only if you use internal OpenTelemetry collector gateway set when you export your logs over insecure (not TLS) connection. It is recommended to run an OpenTelemetry collector in your cluster to server as a gateway to your log vendor or log backend which your use. This redouces the overhead on the application that do not need to encrypt and export to a local receiver in the cluster.
  • OTEL_EXPORTER_OTLP_ENDPOINT/OTEL_EXPORTER_OTLP_LOGS_ENDPOINT - the endpoint of the OpenTelemetry collector to which you want to export your logs. recommended: local OpenTelemetry collector gateway in your cluster. You can also use and otlp compatible receiver like you log vendor endpoints.
  • OTEL_EXPORTER_OTLP_HEADERS / OTEL_EXPORTER_OTLP_LOGS_HEADERS - If you use a vendor, you might get instructions to add some headers to your requests for authentication or other purposes. This is what you need to use for it to work.

The rest of the configuration has reasonable defaults, but you can find browse through them to fine tune to your needs.

Motivation

Out of the 3 pillars of observability, logging is the most mature one. There are many logging libraries and frameworks, and many of them are already used in production. A popular framework for logging in go is zap logger which this project targets. There are also existing practices and standards for how to write logs pipelines that ship these logs to some destination where they are later processed and indexed to aid in system operation tasks.

OpenTelemetry is a new and modern standard for observability, and it is still in its infancy. The OpenTelemetry ecosystem offers a rich set of tools to deliver a high quality observability implementation for modern cloud systems, such as:

  • OpenTelemetry Collector - a high performance, vendor agnostic, logs processing pipeline with dozens of processors and exporters.
  • OTLP protocol - a vendor agnostic, high performance, modern logs protocol.
  • Unified framework - one framework to handle all observability signals in a consistent and unified matter.

and many more.

OpenTelemetry defines the Logs Bridge API:

It is provided for logging library authors to build log appenders, which use this API to bridge between existing logging libraries and the OpenTelemetry log data model.

This package is an implementation for such bridge for the zap logger in golang.

Overtime, it is expected that more and more systems will adopt OpenTelemetry as their observability standard. This project suggests another piece of the big pazzel, which is to ship logs directly from the application to the OpenTelemetry Collector, using the OTLP protocol. This is done by converting every log record written to zap logger into an OpenTelemetry log record, and then export it using the standard OpenTelemetry SDK and exporters.

Benefits
  • Minimal change to existing code - you only need to change one place in your code where you initialize the zap logger, and add a single line of code to attach the OpenTelemetry SDK to it.
  • Less format conversion - log agents like fluentbit, logstash, or even the OpenTelemetry collector, usually consume stderr or tails a file to extract log records, and then convert them to a format that can be processed by the new component in the log pipeline. When next component is an OTLP receiver, we can skip this step and convert the zap log records directly to the OpenTelemetry format.
  • No need to maintain agents - no need for sidecar containers or daemonsets to run log agents, which means less resources and less maintenance.
  • Simpler log pipeline - the log pipeline is simpler because it only needs to handle one format, the OpenTelemetry format.
  • Modern - OpenTelemetry is a modern standard, and it is expected to be adopted by more and more systems in the future. This project is a step towards that future.
  • Standard user experience - The OpenTelemetry SDK provides a standard experience for all programming languages and observability signals, including logs. This means that you can use the environment variables you know and love like OTEL_SERVICE_NAME and OTEL_EXPORTER_OTLP_ENDPOINT to your service just as you would do with traces, metrics, in any other otel compliant component in your system.
Drawbacks
  • Experimental - This project is experimental, and it depends on the experimental opentelemetry-logs-go project. There are still many useful features which are missing and no test coverage. It is recommended to use with caution and research the project before using it in production.
  • No log agent - This project does not provide a log agent, which means that you will need to use the OpenTelemetry Collector to receive the logs and process them. This is not a drawback per se, but it is a different approach than what you might be used to.
  • Less performant - This project introduces more overhead on the process that writes the logs, because it needs to convert the log records to the OpenTelemetry format and then use networking to export them from the process. This is not a drawback per se, as these resources were already consumed and payed indirectly by the log agent, but for applications that targeting to squeeze every bit of performance, this might be a drawback.
  • Code change - This project requires a code change to the application, which means that you will need to recompile and redeploy your application. While it is recommended to process as much as possible outside of the process, in OpenTelemetry collectors, application developers will need to maintain this code - bumping versions, debugging issues, etc. Some organizations prefer for these tasks to be handled by the operations team, which needs to setup log pipelines, without touching any line of code.

Mechanism

When you write this in your code:

    logger, _ := zap.NewProduction()

you are creating a zap.Logger instance which is a wrapper around zapcore.Core instance. zapcore.Core is the actual log backend that implement a Write method that outputs a log records to some destination.

This module provides a zapcore.Core implementation that initialize an OpenTelemetry logger SDK, which is then used to export log records to some destination with an opentelemetry exporter.

Alternatives

There are other ways to achieve similar results, and each user should evaluate the options and choose the one that fits best to their needs.

Some alternatives are:

  • Use log agent like fluentbit or logstash, or OpenTelemetry collector to extract logs from the application and convert them to the OpenTelemetry format. This is the most common approach today, and it is being the popular choice for many years. It is a proven approach, and it is recommended to use it if you are not sure.
  • If your system runs in kubernetes, you can setup logging (and metrics and traces if you like) with few minutes using OpenSource projects like Odigos. It basically injects a DaemonSet to collect all logs from each k8s node, ship to an opentelemetry gateway collector which is deployed in the cluster, and then export to all the popular log destinations, SAAS or self managed. Disclaimer: This repo is brought to you by keyval, which is the company behind odigos.
  • If you are just getting started, and not yet ready to deploy a full blown logs pipeline, you can simply use zap.NewProduction and write logs to stdout/stderr or file, and consume them when needed with kubectl logs/docker logs or simply read the file.

Contributing

There are many features and enhancements currently lacking. We welcome and issue and pull request to help improve this project.

Talk to us

This project is brought to you with ❤️ by the odigos team.

We are happy to hear your feedback and answer your questions. Or just say hi and chat about observability.

If you want to try out odigos, with or without this log bridge, let's talk about it too.

You can find us in odigos slack

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AttachToZapLogger

func AttachToZapLogger(logger *zap.Logger) *zap.Logger

TODO: I guess there is more idomatic way to do this in go

func NewOtelZapCore

func NewOtelZapCore() zapcore.Core

this function creates a new zapcore.Core that can be used with zap.New() this instance will translate zap logs to opentelemetry logs and export them

serviceName is required and is used as a resource attribute in the reported telemetry. TODO: we should probably extend this to support more options like additional resource attributes TODO2: should we also support a way to configure other components and configuration options? like exporters, processors, etc. Currently a user can configure the SDK only via environment variables which is fair enough but advanced users might want more control.

Types

type OtelZapCore

type OtelZapCore struct {
	zapcore.Core
	// contains filtered or unexported fields
}

func (*OtelZapCore) Check

func (*OtelZapCore) Enabled

func (o *OtelZapCore) Enabled(zapcore.Level) bool

TODO: see how it is implemented in zapcore and consider adding it here as well

func (*OtelZapCore) With

func (o *OtelZapCore) With(fields []zapcore.Field) zapcore.Core

TODO: implement this and add the fields to each new log record created

func (*OtelZapCore) Write

func (o *OtelZapCore) Write(ent zapcore.Entry, fields []zapcore.Field) error

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL