14 - Observability
The plugin SDK provides built-in observability support through structured logging and metrics. This allows you to instrument your plugins for debugging and monitoring without managing the infrastructure yourself.
Logger
The SDK injects a logger into the context of every CRUD operation. The logger is pre-configured with plugin metadata (namespace, operation, resource type).
Getting the Logger
func (p *Plugin) Create(ctx context.Context, req *resource.CreateRequest) (*resource.CreateResult, error) {
log := plugin.LoggerFromContext(ctx)
log.Info("creating file resource", "label", req.Label)
// ... implementation ...
}
Log Levels
Use the appropriate level for your message:
| Level | Use When |
|---|---|
Debug |
Verbose details useful during development |
Info |
Normal operations (resource created, updated) |
Warn |
Recoverable issues (retry, fallback used) |
Error |
Failures that affect operation outcome |
log.Debug("parsing properties", "raw", string(req.Properties))
log.Info("upload started", "path", props.Path)
log.Warn("retrying after timeout", "attempt", 2)
log.Error("failed to connect", "error", err.Error())
Adding Context with With()
Create a child logger with additional fields that persist across calls:
log := plugin.LoggerFromContext(ctx)
log = log.With("requestID", requestID)
log.Info("operation started") // includes requestID
log.Debug("step 1 complete") // includes requestID
Configuring Log Level
Plugin logs are captured by the agent and follow the agent's logging configuration. See the Logging section in the configuration guide to adjust log levels.
Metrics
The SDK also provides a MetricRegistry for recording custom metrics. These are exported via OpenTelemetry when enabled in the agent.
Getting the MetricRegistry
func (p *Plugin) Create(ctx context.Context, req *resource.CreateRequest) (*resource.CreateResult, error) {
metrics := plugin.MetricsFromContext(ctx)
// Record a counter
metrics.Counter("sftp.uploads_started", 1,
attribute.String("path", props.Path))
// ... implementation ...
}
Available Metric Types
The SDK exposes four metric types from OpenTelemetry's metric model:
// Counter - monotonically increasing value
metrics.Counter("sftp.uploads_total", 1,
attribute.String("status", "success"))
// UpDownCounter - value that can increase or decrease
metrics.UpDownCounter("sftp.active_connections", 1) // connection opened
metrics.UpDownCounter("sftp.active_connections", -1) // connection closed
// Gauge - point-in-time measurement
metrics.Gauge("sftp.queue_depth", float64(len(queue)))
// Histogram - distribution of values (durations, sizes)
start := time.Now()
// ... operation ...
metrics.Histogram("sftp.upload_duration_ms",
float64(time.Since(start).Milliseconds()))
Complete Example
Here's the SFTP plugin's Create method with observability:
func (p *Plugin) Create(ctx context.Context, req *resource.CreateRequest) (*resource.CreateResult, error) {
// Get observability from context
log := plugin.LoggerFromContext(ctx)
metrics := plugin.MetricsFromContext(ctx)
log.Info("creating file resource", "label", req.Label)
// Parse file properties from request
props, err := parseFileProperties(req.Properties)
if err != nil {
log.Error("invalid properties", "error", err.Error())
return &resource.CreateResult{
ProgressResult: &resource.ProgressResult{
Operation: resource.OperationCreate,
OperationStatus: resource.OperationStatusFailure,
ErrorCode: resource.OperationErrorCodeInvalidRequest,
StatusMessage: err.Error(),
},
}, nil
}
// ... client setup ...
// Start async upload
requestID := client.StartUpload(props.Path, props.Content, perm)
// Record metric for uploads started
metrics.Counter("sftp.uploads_started", 1,
attribute.String("path", props.Path))
log.Debug("upload started", "requestID", requestID, "path", props.Path)
return &resource.CreateResult{
ProgressResult: &resource.ProgressResult{
Operation: resource.OperationCreate,
OperationStatus: resource.OperationStatusInProgress,
RequestID: requestID,
NativeID: props.Path,
},
}, nil
}
No-Op Fallback
If no logger or metrics are configured (e.g., during unit tests), LoggerFromContext and MetricsFromContext return no-op implementations that safely ignore all calls. Your code doesn't need nil checks.
Previous: 13 - Local Testing | Next: 15 - Real-World Plugins