A lean CLI wrapper around the official OpenTelemetry Transformation Language (OTTL) library for testing and applying transformations to telemetry data.
ottl provides a simple, focused tool for working with OTTL transformations outside of a full OpenTelemetry Collector deployment. Perfect for development, testing, debugging, and CI/CD validation of telemetry transformations.
Key Features:
- Ultra-lean: Single 175-line application with minimal dependencies
- Official compatibility: Uses official OpenTelemetry packages for 100% compatibility
- Fast: Sub-100ms transformations with ~20MB binary size
- Unix-friendly: Pipe-friendly design that integrates seamlessly into shell workflows
- Production-ready: Comprehensive error handling and clear diagnostics
Download the latest release from GitHub Releases:
# Linux (amd64)
curl -L https://github.com/telemetrydrops/ottl/releases/latest/download/ottl_linux_amd64.tar.gz | tar xz
sudo mv ottl /usr/local/bin/
# Linux (arm64)
curl -L https://github.com/telemetrydrops/ottl/releases/latest/download/ottl_linux_arm64.tar.gz | tar xz
sudo mv ottl /usr/local/bin/git clone https://github.com/telemetrydrops/ottl.git
cd ottl
make buildBinary will be available at ./bin/ottl
# Apply a simple attribute transformation
echo 'set(attributes["environment"], "production")' | ottl transform --input-file trace.json# Set an attribute
echo 'set(attributes["service.env"], "prod")' | ottl transform -i spans.json
# Delete an attribute
echo 'delete_key(attributes, "internal.debug")' | ottl transform -i spans.json
# Conditional transformation
echo 'set(attributes["error"], true) where status.code == 2' | ottl transform -i spans.json
# Update span name
echo 'set(name, "updated-span-name")' | ottl transform -i spans.jsonottl transform --input-file <path> < statement.ottlInput Methods:
- OTTL statement: Via stdin (pipe or redirect)
- Telemetry data: Via
--input-fileflag (OTLP JSON format)
Output:
- Transformed JSON to stdout
- Error messages to stderr
ottl expects OTLP JSON format for trace data:
{
"resourceSpans": [
{
"resource": {
"attributes": [
{
"key": "service.name",
"value": {"stringValue": "my-service"}
}
]
},
"scopeSpans": [
{
"scope": {
"name": "my.library",
"version": "1.0.0"
},
"spans": [
{
"traceId": "5B8EFFF798038103D269B633813FC60C",
"spanId": "EEE19B7EC3C1B174",
"name": "example-span",
"attributes": [
{
"key": "http.method",
"value": {"stringValue": "GET"}
}
]
}
]
}
]
}
]
}# Set string attribute
echo 'set(attributes["env"], "production")' | ottl transform -i spans.json
# Set boolean attribute
echo 'set(attributes["is_error"], true)' | ottl transform -i spans.json
# Copy attribute value
echo 'set(attributes["method"], attributes["http.method"])' | ottl transform -i spans.json
# Delete attribute
echo 'delete_key(attributes, "sensitive_data")' | ottl transform -i spans.json
# Delete matching attributes
echo 'delete_matching_keys(attributes, "internal.*")' | ottl transform -i spans.json# Set attribute only for specific spans
echo 'set(attributes["processed"], true) where name == "http-request"' | ottl transform -i spans.json
# Mark error spans
echo 'set(attributes["has_error"], true) where status.code == 2' | ottl transform -i spans.json
# Update based on attribute value
echo 'set(name, "external-call") where attributes["http.host"] != nil' | ottl transform -i spans.json# Replace pattern in span name
echo 'set(name, replace_pattern(name, "/api/v[0-9]+", "/api"))' | ottl transform -i spans.json
# Concatenate values
echo 'set(attributes["full_path"], concat(attributes["http.method"], " ", attributes["http.target"]))' | ottl transform -i spans.json
# Convert to uppercase
echo 'set(attributes["method_upper"], uppercase(attributes["http.method"]))' | ottl transform -i spans.json# Set span status
echo 'set(status.code, 2)' | ottl transform -i spans.json
echo 'set(status.message, "Internal server error")' | ottl transform -i spans.json
# Update span kind
echo 'set(kind, 3)' | ottl transform -i spans.json # CLIENT kind#!/bin/bash
# validate-transformations.sh
TRACE_FILE="sample-trace.json"
TRANSFORMATIONS=("transforms/add-env.ottl" "transforms/cleanup-attrs.ottl")
for transform in "${TRANSFORMATIONS[@]}"; do
echo "Testing transformation: $transform"
if cat "$transform" | ottl transform -i "$TRACE_FILE" > /dev/null; then
echo "✓ $transform passed"
else
echo "✗ $transform failed"
exit 1
fi
done# GitHub Actions example
name: Validate OTTL Transformations
on:
pull_request:
paths:
- 'transformations/**/*.ottl'
- 'test-data/**/*.json'
jobs:
test-transformations:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Download ottl
run: |
curl -L https://github.com/telemetrydrops/ottl/releases/latest/download/ottl_linux_amd64.tar.gz | tar xz
chmod +x ottl
- name: Test transformations
run: |
for transform in transformations/*.ottl; do
echo "Testing $transform"
./ottl transform -i test-data/sample-trace.json < "$transform" | jq empty
done# Development script for testing transformations
function test-ottl() {
local statement="$1"
local input_file="${2:-./test-data/trace.json}"
echo "Testing: $statement"
echo "$statement" | ottl transform -i "$input_file" | jq '.resourceSpans[0].scopeSpans[0].spans[0].attributes'
}
# Usage examples
test-ottl 'set(attributes["env"], "dev")'
test-ottl 'delete_key(attributes, "debug")'
test-ottl 'set(name, "updated-name")' ./custom-trace.json# Extract specific data after transformation
echo 'set(attributes["processed"], true)' | ottl transform -i trace.json | \
jq -r '.resourceSpans[].scopeSpans[].spans[].attributes[] | select(.key == "processed") | .value.stringValue'
# Save only transformed spans
echo 'set(attributes["env"], "prod")' | ottl transform -i input.json > processed-trace.json
# Count spans after filtering transformation
echo 'delete_key(attributes, "debug") where attributes["debug"] != nil' | \
ottl transform -i trace.json | jq '.resourceSpans[].scopeSpans[].spans | length'ottl is optimized for fast iteration and development workflows:
- Startup time: < 50ms
- Small files (< 1MB): < 100ms processing time
- Memory usage: < 100MB for typical operations
- Binary size: ~20MB (includes full OTTL function library)
- Use specific transformations: Avoid overly broad patterns that match many spans
- Test with representative data: Use realistic file sizes for performance testing
- Pipeline efficiently: Chain operations using Unix pipes rather than multiple invocations
ottl provides clear, actionable error messages:
Invalid JSON Input:
$ echo 'set(attributes["env"], "prod")' | ottl transform -i malformed.json
Error: invalid OTLP JSON format: invalid character '}' looking for beginning of valueOTTL Syntax Error:
$ echo 'set(invalid syntax)' | ottl transform -i trace.json
Error: failed to parse OTTL statement 'set(invalid syntax)': expected ( after function nameFile Not Found:
$ echo 'set(attributes["env"], "prod")' | ottl transform -i missing.json
Error: cannot open file missing.json: no such file or directoryRuntime Transformation Error:
$ echo 'set(nonexistent_field["key"], "value")' | ottl transform -i trace.json
Error: failed to execute transformation on span: invalid path expressionFor debugging transformations, use jq to inspect intermediate results:
# Check input structure
cat trace.json | jq '.resourceSpans[0].scopeSpans[0].spans[0]'
# Verify transformation result
echo 'set(attributes["debug"], true)' | ottl transform -i trace.json | \
jq '.resourceSpans[0].scopeSpans[0].spans[0].attributes'Issue: Empty output
- Cause: Input file contains no spans or invalid structure
- Solution: Validate input with
jq '.resourceSpans | length'
Issue: Transformation not applied
- Cause: OTTL statement doesn't match any spans
- Solution: Test with simpler statements or verify span structure
Issue: Performance problems with large files
- Cause: File size exceeds memory limits
- Solution: Split files or use streaming tools like
jq --stream
ottl has minimal dependencies:
require (
github.com/spf13/cobra v1.9.1 // CLI framework
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl v0.132.0 // OTTL library
go.opentelemetry.io/collector/pdata v1.38.0 // OTLP data structures
go.opentelemetry.io/collector/component/componenttest v0.132.0 // Testing utilities
)All dependencies are official OpenTelemetry packages, ensuring 100% compatibility with the OpenTelemetry ecosystem.
# Build binary
make build
# Run tests
make test
# Format code
make fmt
# Run example transformation
make example# Run all tests
go test -v ./...
# Test with actual data
echo 'set(attributes["test"], true)' | ./bin/ottl transform -i ./local/payload-examples/trace.json- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Run
make testandmake fmt - Submit a pull request
ottl supports all standard OTTL functions for span context:
Attribute Functions:
set(),delete_key(),delete_matching_keys()keep_keys(),truncate_all(),limit()
String Functions:
replace_pattern(),replace_all_patterns(),replace_all_matches()concat(),split(),join(),substring()uppercase(),lowercase()
Conversion Functions:
string(),int(),double(),bool()
Conditional Functions:
whereclauses for conditional execution
Math Functions:
- Basic arithmetic:
+,-,*,/,%
Comparison Operators:
==,!=,<,<=,>,>=
Logical Operators:
and,or,not
For complete function reference, see the OTTL Language Documentation.
- OpenTelemetry Collector - Full-featured telemetry collection and processing
- OTTL Documentation - Official OTTL language specification
- Jaeger - Distributed tracing platform
- Prometheus - Monitoring and alerting toolkit
Apache License 2.0 - see LICENSE file for details.
Built with and thanks to:
- OpenTelemetry Community for the excellent OTTL library
- Cobra for CLI framework
- All contributors and users providing feedback
Questions or issues? Please open an issue on GitHub.