Simple and elegant wrapping of Elastic APM (Application Performance Monitoring) in Go, based on go.elastic.co/apm/v2.
π― Simple APM Setup: Config struct-based initialization with less boilerplate code β‘ Zap Logging Integration: Built-in Zap logging support with APM context tracking π gRPC Distributed Tracing: W3C trace headers propagation across gRPC boundaries π Environment Variables: Automatic environment setup with override controls π Version Matching: Ensures v2 usage preventing v1/v2 mixing pitfalls
go get github.com/go-xlan/elasticapm- Go 1.23.0 and above
- Elastic APM Server v2.x
This example shows complete APM setup with transaction tracking and span instrumentation:
package main
import (
"time"
"github.com/go-xlan/elasticapm"
"github.com/yyle88/must"
"github.com/yyle88/zaplog"
"go.elastic.co/apm/v2"
"go.uber.org/zap"
)
func main() {
// Initialize zap logging first
zaplog.SUG.Info("Starting APM demo")
// Configure APM settings
cfg := &elasticapm.Config{
Environment: "development",
ServerUrl: "http://localhost:8200",
ServiceName: "demo-basic-service",
ServiceVersion: "1.0.0",
SkipShortSpans: false, // Capture all spans
}
// Initialize APM
must.Done(elasticapm.Initialize(cfg))
defer elasticapm.Close()
zaplog.SUG.Info("APM initialized", zap.String("version", elasticapm.GetApmAgentVersion()))
// Verify version compatibility
if elasticapm.CheckApmAgentVersion(apm.AgentVersion) {
zaplog.SUG.Info("APM version check passed")
}
// Start a transaction
txn := apm.DefaultTracer().StartTransaction("demo-operation", "custom")
defer txn.End()
zaplog.SUG.Info("Transaction started", zap.String("transaction_id", txn.TraceContext().Trace.String()))
// Simulate some work with a span
span := txn.StartSpan("process-data", "internal", nil)
processData()
span.End()
// Second span simulating database operation
dbSpan := txn.StartSpan("database-query", "db.query", nil)
dbSpan.Context.SetDatabase(apm.DatabaseSpanContext{
Statement: "SELECT * FROM users WHERE id = ?",
Type: "sql",
})
simulateDatabaseOperation()
dbSpan.End()
zaplog.SUG.Info("Demo completed")
}
func processData() {
// Simulate data processing
time.Sleep(100 * time.Millisecond)
zaplog.SUG.Debug("Data processed")
}
func simulateDatabaseOperation() {
// Simulate database operation
time.Sleep(50 * time.Millisecond)
zaplog.SUG.Debug("Database operation executed")
}β¬οΈ Source: Source
This example demonstrates W3C trace headers propagation across gRPC service boundaries:
package main
import (
"context"
"time"
"github.com/go-xlan/elasticapm"
"github.com/yyle88/must"
"github.com/yyle88/zaplog"
"go.uber.org/zap"
)
func main() {
// Initialize zap logging
zaplog.SUG.Info("Starting gRPC APM demo")
// Configure APM
cfg := &elasticapm.Config{
Environment: "development",
ServerUrl: "http://localhost:8200",
ServiceName: "demo-grpc-client",
ServiceVersion: "1.0.0",
}
// Initialize APM
must.Done(elasticapm.Initialize(cfg))
defer elasticapm.Close()
zaplog.SUG.Info("APM initialized for gRPC demo")
// Simulate gRPC client call with distributed tracing
ctx := context.Background()
callRemoteService(ctx)
zaplog.SUG.Info("gRPC demo completed")
}
func callRemoteService(ctx context.Context) {
// Start APM transaction with gRPC outgoing context
// The trace context will be auto injected into gRPC metadata
txn, tracedCtx := elasticapm.StartApmTraceGrpcOutgoingCtx(
ctx,
"grpc-call-remote-service",
"request",
)
defer txn.End()
zaplog.SUG.Info("Starting gRPC call",
zap.String("trace_id", txn.TraceContext().Trace.String()),
zap.String("transaction_id", txn.TraceContext().Span.String()),
)
// Simulate preparing request
prepareSpan := txn.StartSpan("prepare-request", "internal", nil)
prepareRequest()
prepareSpan.End()
// Simulate gRPC call
// In production code, you would pass tracedCtx to your gRPC client call:
// response, err := grpcClient.Method(tracedCtx, request)
grpcSpan := txn.StartSpan("grpc.Call", "external.grpc", nil)
simulateGrpcCall(tracedCtx)
grpcSpan.End()
// Simulate processing response
processSpan := txn.StartSpan("process-response", "internal", nil)
processResponse()
processSpan.End()
zaplog.SUG.Info("gRPC call completed")
}
func prepareRequest() {
// Simulate request preparation
time.Sleep(20 * time.Millisecond)
zaplog.SUG.Debug("Request prepared")
}
func simulateGrpcCall(ctx context.Context) {
// Simulate gRPC network call
// The trace context is already in the metadata thanks to StartApmTraceGrpcOutgoingCtx
time.Sleep(100 * time.Millisecond)
zaplog.SUG.Debug("gRPC call executed")
}
func processResponse() {
// Simulate response processing
time.Sleep(30 * time.Millisecond)
zaplog.SUG.Debug("Response processed")
}β¬οΈ Source: Source
| Field | Type | Description |
|---|---|---|
Environment |
string |
Environment name (e.g., "production", "staging") |
ServerUrl |
string |
Single APM server URL |
ServerUrls |
[]string |
Multiple APM server URLs |
ApiKey |
string |
API key to authenticate with APM server |
SecretToken |
string |
Secret token to authenticate with APM server |
ServiceName |
string |
Name that identifies this service |
ServiceVersion |
string |
Version of this service |
NodeName |
string |
Name of node in multi-instance setup |
ServerCertPath |
string |
Path to server certificate |
SkipShortSpans |
bool |
Skip spans shorter than threshold |
Initialize(cfg *Config) error- Initialize APM with default optionsInitializeWithOptions(cfg *Config, evo *EnvOption, setEnvs ...func()) error- Initialize with custom optionsClose()- Flush and close APM tracingSetLog(LOG apm.Logger)- Set custom logging
GetApmAgentVersion() string- Get current APM agent versionCheckApmAgentVersion(agentVersion string) bool- Verify version matching
StartApmTraceGrpcOutgoingCtx(ctx, name, apmTxnType) (*apm.Transaction, context.Context)- Start traced gRPC callContextWithTraceGrpcOutgoing(ctx, apmTransaction) context.Context- Add trace to contextContextWithGrpcOutgoingTrace(ctx, apmTraceContext) context.Context- Add trace context to outgoing metadata
The package supports configuration through environment variables. You can control whether to override existing variables:
cfg := &elasticapm.Config{
Environment: "production",
ServerUrl: "http://localhost:8200",
ServiceName: "my-service",
ServiceVersion: "1.0.0",
}
envOption := &elasticapm.EnvOption{
Override: true, // Override existing environment variables
}
must.Done(elasticapm.InitializeWithOptions(cfg, envOption))
defer elasticapm.Close()Integrate with custom zap logging setup:
import (
"github.com/go-xlan/elasticapm"
"github.com/go-xlan/elasticapm/apmzaplog"
)
// Initialize APM with custom zap logging
must.Done(elasticapm.Initialize(cfg))
elasticapm.SetLog(apmzaplog.NewLog())
defer elasticapm.Close()When working with microservices, trace context needs to be passed between services:
// Service A: Start transaction
txn := apm.DefaultTracer().StartTransaction("external-call", "request")
ctx := apm.ContextWithTransaction(context.Background(), txn)
// Inject trace context into gRPC metadata
ctx = elasticapm.ContextWithTraceGrpcOutgoing(ctx, txn)
// Make gRPC call
response := grpcClient.Method(ctx, request)
txn.End()Choose service names that reflect their function:
- Use lowercase with hyphens:
user-service,payment-gateway - Include team name when multiple teams share infrastructure:
team-a-user-service - Keep names concise but descriptive
Set distinct environments to separate traces:
development- Development on your machinestaging- Pre-production testingproduction- Live production traffictesting- Automated test runs
To reduce overhead in high-throughput applications:
cfg := &elasticapm.Config{
Environment: "production",
ServerUrl: "http://localhost:8200",
ServiceName: "my-service",
SkipShortSpans: true, // Skip spans shorter than threshold
}Always include semantic version in service configuration:
cfg := &elasticapm.Config{
ServiceVersion: "1.2.3", // Semantic version
}This helps correlate performance changes with deployments.
If APM cannot connect to the server:
- Verify the server URL is accessible
- Check firewall settings allow outbound connections
- Verify API key is correct
- Check APM server logs
If traces are not appearing in Kibana:
- Ensure transaction is ended:
defer txn.End() - Verify service name matches in Kibana filters
- Check environment setting matches what you expect
- Call
Close()to flush pending data before exit
If you see errors about missing symbols:
# Check all dependencies use v2
go list -m all | grep elasticEnsure all imports use go.elastic.co/apm/v2, not go.elastic.co/apm.
This package requires v2: go.elastic.co/apm/v2. It will not work with the v1.x package go.elastic.co/apm.
The version check functions ensure all dependencies use v2 to avoid the common pitfall of mixing v1 and v2 packages, which maintain separate tracing instances.
MIT License. See LICENSE.
Contributions are welcome! Report bugs, suggest features, and contribute code:
- π Found a mistake? Open an issue on GitHub with reproduction steps
- π‘ Have a feature idea? Create an issue to discuss the suggestion
- π Documentation confusing? Report it so we can improve
- π Need new features? Share the use cases to help us understand requirements
- β‘ Performance issue? Help us optimize through reporting slow operations
- π§ Configuration problem? Ask questions about complex setups
- π’ Follow project progress? Watch the repo to get new releases and features
- π Success stories? Share how this package improved the workflow
- π¬ Feedback? We welcome suggestions and comments
New code contributions, follow this process:
- Fork: Fork the repo on GitHub (using the webpage UI).
- Clone: Clone the forked project (
git clone https://github.com/yourname/repo-name.git). - Navigate: Navigate to the cloned project (
cd repo-name) - Branch: Create a feature branch (
git checkout -b feature/xxx). - Code: Implement the changes with comprehensive tests
- Testing: (Golang project) Ensure tests pass (
go test ./...) and follow Go code style conventions - Documentation: Update documentation to support client-facing changes and use significant commit messages
- Stage: Stage changes (
git add .) - Commit: Commit changes (
git commit -m "Add feature xxx") ensuring backward compatible code - Push: Push to the branch (
git push origin feature/xxx). - PR: Open a merge request on GitHub (on the GitHub webpage) with detailed description.
Please ensure tests pass and include relevant documentation updates.
Welcome to contribute to this project via submitting merge requests and reporting issues.
Project Support:
- β Give GitHub stars if this project helps you
- π€ Share with teammates and (golang) programming friends
- π Write tech blogs about development tools and workflows - we provide content writing support
- π Join the ecosystem - committed to supporting open source and the (golang) development scene
Have Fun Coding with this package! πππ