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.