Skip to main content
Version: 2.8.x(Latest)

Introduction

The code development of monitoring metrics directly uses the gmetric component of the main library of the framework. However, since the gmetric component actually only defines the relevant interfaces for monitoring metrics and provides the default NoopPerformer, the default monitoring metrics feature is turned off. Therefore, it is necessary to introduce specific interface implementation components to truly enable the monitoring metrics feature. The framework community provides the community component github.com/gogf/gf/contrib/metric/otelmetric/v2, which uses OpenTelemetry to implement the monitoring metrics interface of the framework. By introducing this community component and executing the creation of monitoring metrics management objects, the monitoring metrics feature can be enabled. The source code address of the otelmetric component: https://github.com/gogf/gf/tree/master/contrib/metric/otelmetric

We will introduce the basic usage of the monitoring metrics component through a simple monitoring metrics implementation example.

package main

import (
"go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"

"github.com/gogf/gf/contrib/metric/otelmetric/v2"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/os/gmetric"
)

var (
meter = gmetric.GetGlobalProvider().Meter(gmetric.MeterOption{
Instrument: "github.com/gogf/gf/example/metric/basic",
InstrumentVersion: "v1.0",
})
counter = meter.MustCounter(
"goframe.metric.demo.counter",
gmetric.MetricOption{
Help: "This is a simple demo for Counter usage",
Unit: "bytes",
},
)
)

func main() {
var (
ctx = gctx.New()
reader = metric.NewManualReader()
)

provider := otelmetric.MustProvider(otelmetric.WithReader(reader))
provider.SetAsGlobal()
defer provider.Shutdown(ctx)

counter.Inc(ctx)
counter.Add(ctx, 10)

var (
rm = metricdata.ResourceMetrics{}
err = reader.Collect(ctx, &rm)
)
if err != nil {
g.Log().Fatal(ctx, err)
}
g.DumpJson(rm)
}

Creation of Metric Management Components

The global monitoring metric management object can be obtained through the gmetric.GetGlobalProvider() method. This object is a singleton design, and there can only be one globally. The Meter method of this object can be used to create/get the corresponding component object. The component object is used to manage all the monitoring metrics under this component. When creating a component object, it is usually necessary to define the name and version of the component (Instrument) (although it can also be empty, it is recommended to set it for easy maintenance in the future).

meter = gmetric.GetGlobalProvider().Meter(gmetric.MeterOption{
Instrument: "github.com/gogf/gf/example/metric/basic",
InstrumentVersion: "v1.0",
})

The gmeter.MeterOption data structure is as follows:

// MeterOption holds the creation option for a Meter.
type MeterOption struct {
// Instrument is the instrumentation name to bind this Metric to a global MeterProvider.
// This is an optional configuration for a metric.
Instrument string

// InstrumentVersion is the instrumentation version to bind this Metric to a global MeterProvider.
// This is an optional configuration for a metric.
InstrumentVersion string

// Attributes holds the constant key-value pair description metadata for all metrics of Meter.
// This is an optional configuration for a meter.
Attributes Attributes
}

Creation of Monitoring Metric Objects

Through the Meter interface object, we can create the corresponding various metrics under this component. Metrics have various data types, so the definition of the Meter interface is as follows:

// Meter hold the functions for kinds of Metric creating.
type Meter interface {
// Counter creates and returns a new Counter.
Counter(name string, option MetricOption) (Counter, error)

// UpDownCounter creates and returns a new UpDownCounter.
UpDownCounter(name string, option MetricOption) (UpDownCounter, error)

// Histogram creates and returns a new Histogram.
Histogram(name string, option MetricOption) (Histogram, error)

// ObservableCounter creates and returns a new ObservableCounter.
ObservableCounter(name string, option MetricOption) (ObservableCounter, error)

// ObservableUpDownCounter creates and returns a new ObservableUpDownCounter.
ObservableUpDownCounter(name string, option MetricOption) (ObservableUpDownCounter, error)

// ObservableGauge creates and returns a new ObservableGauge.
ObservableGauge(name string, option MetricOption) (ObservableGauge, error)

// MustCounter creates and returns a new Counter.
// It panics if any error occurs.
MustCounter(name string, option MetricOption) Counter

// MustUpDownCounter creates and returns a new UpDownCounter.
// It panics if any error occurs.
MustUpDownCounter(name string, option MetricOption) UpDownCounter

// MustHistogram creates and returns a new Histogram.
// It panics if any error occurs.
MustHistogram(name string, option MetricOption) Histogram

// MustObservableCounter creates and returns a new ObservableCounter.
// It panics if any error occurs.
MustObservableCounter(name string, option MetricOption) ObservableCounter

// MustObservableUpDownCounter creates and returns a new ObservableUpDownCounter.
// It panics if any error occurs.
MustObservableUpDownCounter(name string, option MetricOption) ObservableUpDownCounter

// MustObservableGauge creates and returns a new ObservableGauge.
// It panics if any error occurs.
MustObservableGauge(name string, option MetricOption) ObservableGauge

// RegisterCallback registers callback on certain metrics.
// A callback is bound to certain component and version, it is called when the associated metrics are read.
// Multiple callbacks on the same component and version will be called by their registered sequence.
RegisterCallback(callback Callback, canBeCallbackMetrics ...ObservableMetric) error

// MustRegisterCallback performs as RegisterCallback, but it panics if any error occurs.
MustRegisterCallback(callback Callback, canBeCallbackMetrics ...ObservableMetric)
}

Taking the meter.MustCounter method used in this example code as an introduction, this method creates a Counter synchronous metric, and since we lazily use the Must* method, it means that if the creation of the metric fails, then this method will panic with an error. When creating a metric object, the metric name name is a required parameter, and the other MetricOption is optional, used to further describe the metric information. The data structure of MetricOption is defined as follows:

// MetricOption holds the basic options for creating a metric.
type MetricOption struct {
// Help provides information about this Histogram.
// This is an optional configuration for a metric.
Help string

// Unit is the unit for metric value.
// This is an optional configuration for a metric.
Unit string

// Attributes holds the constant key-value pair description metadata for this metric.
// This is an optional configuration for a metric.
Attributes Attributes

// Buckets defines the buckets into which observations are counted.
// For Histogram metric only.
// A histogram metric uses default buckets if no explicit buckets configured.
Buckets []float64

// Callback function for metric, which is called when metric value changes.
// For observable metric only.
// If an observable metric has either Callback attribute nor global callback configured, it does nothing.
Callback MetricCallback
}

Initialization of Monitoring Metric Implementation

The creation and initialization of the monitoring metric management object can be done through the otelmetric.MustProvider creation method.

provider := otelmetric.MustProvider(otelmetric.WithReader(reader))
provider.SetAsGlobal()
defer provider.Shutdown(ctx)

As introduced earlier, GlobalProvider is actually a singleton metric management object, so the provider.SetAsGlobal method call here can set the object as a global metric management object, which facilitates the subsequent creation of metrics based on this object.

We call the defer provider.ShutDown method in the main function to facilitate a graceful shutdown of the metric management object when the program ends, allowing, for example, the timely output of indicator caches to the target end.

Usage of Monitoring Metric Objects

Different metric objects have different operational methods for implementing changes in metric values. Taking the Counter metric type used in the example as an example, its interface is defined as follows:

// Counter is a Metric that represents a single numerical value that can ever
// goes up.
type Counter interface {
Metric
CounterPerformer
}

// CounterPerformer performs operations for Counter metric.
type CounterPerformer interface {
// Inc increments the counter by 1. Use Add to increment it by arbitrary
// non-negative values.
Inc(ctx context.Context, option ...Option)

// Add adds the given value to the counter. It panics if the value is < 0.
Add(ctx context.Context, increment float64, option ...Option)
}

As can be seen, the Counter metric can primarily perform two operation methods, Inc and Add. There is also a Metric interface, and all metrics implement this interface to obtain basic information of the current metric. The interface is defined as follows:

// Metric models a single sample value with its metadata being exported.
type Metric interface {
// Info returns the basic information of a Metric.
Info() MetricInfo
}

// MetricInfo exports information of the Metric.
type MetricInfo interface {
Key() string // Key returns the unique string key of the metric.
Name() string // Name returns the name of the metric.
Help() string // Help returns the help description of the metric.
Unit() string // Unit returns the unit name of the metric.
Type() MetricType // Type returns the type of the metric.
Attributes() Attributes // Attributes returns the constant attribute slice of the metric.
Instrument() InstrumentInfo // InstrumentInfo returns the instrument info of the metric.
}

// InstrumentInfo exports the instrument information of a metric.
type InstrumentInfo interface {
Name() string // Name returns the instrument name of the metric.
Version() string // Version returns the instrument version of the metric.
}

Data Reading of Monitoring Metrics

Through the OpenTelemetry component relationship introduced in the previous chapter, we know that if you want to use metrics, you must use a MetricReader. Therefore, in this example code, we use the most commonly used ManualReader in the open-source components of the OpenTelemetry official community to simply implement metric data reading. The ManualReader is provided by the OpenTelemetry official community.

reader = metric.NewManualReader()

And configured to Provider through the WithReader method:

provider := otelmetric.MustProvider(otelmetric.WithReader(reader))

Then, the current metric data can be obtained through the Collect method:

var (
rm = metricdata.ResourceMetrics{}
err = reader.Collect(ctx, &rm)
)
if err != nil {
g.Log().Fatal(ctx, err)
}
g.DumpJson(rm)

After execution, the terminal output:

...
"ScopeMetrics": [
{
"Scope": {
"Name": "github.com/gogf/gf/example/metric/basic",
"Version": "v1.0",
"SchemaURL": ""
},
"Metrics": [
{
"Name": "goframe.metric.demo.counter",
"Description": "This is a simple demo for Counter usage",
"Unit": "bytes",
"Data": {
"DataPoints": [
{
"Attributes": [],
"StartTime": "2024-03-25T10:13:19.326977+08:00",
"Time": "2024-03-25T10:13:19.327144+08:00",
"Value": 11
}
],
"Temporality": "CumulativeTemporality",
"IsMonotonic": true
}
}
]
},
...

To simplify the example introduction, we have omitted some output contents here. For more detailed metric and output descriptions, please refer to the following chapters.