OTTL Transformation Generator & Simulator
Pricing
from $100.00 / 1,000 ottl request processeds
OTTL Transformation Generator & Simulator
Plain-English requests → OpenTelemetry OTTL (transform/filter processors), an OTTL linter with 56 rules, and a simulator that shows before/after diffs on sample traces, logs and metrics. Deterministic, no LLM; Standby HTTP for agents.
Pricing
from $100.00 / 1,000 ottl request processeds
Rating
0.0
(0)
Developer
Justin Hagerty
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
0
Monthly active users
11 days ago
Last modified
Categories
Share
Describe the telemetry change you want in plain English and get a ready-to-paste OpenTelemetry Collector transform / filter processor in OTTL, linted by 66 rules and executed against sample traces, logs and metrics with before/after diffs — before it ever touches production.
The OpenTelemetry Transformation Language (OTTL) is how the Collector redacts PII, drops noisy spans, renames attributes and reshapes metrics. It is also easy to get subtly wrong: a regex written for Python instead of RE2, a $ that YAML expansion eats, a statement in the wrong context, a delete_key that runs before the set that needed the value, a filter condition that silently drops 90% of your traces, error_mode: propagate dropping whole batches. None of these fail loudly.
This Actor is a deterministic OTTL studio: it recognises 61 common transformation intents, generates OTTL in the syntax your Collector version understands, validates both generated and hand-written OTTL, and simulates the statements on realistic telemetry so you see exactly what changes, what gets dropped and what breaks. No LLM, no network calls, same answer every time — as a normal Apify run, in CI, or over Standby HTTP for AI coding agents and MCP clients.
Why use it
- Plain English → correct OTTL. "Remove Kubernetes pod UIDs, redact user email addresses, and drop successful health-check spans" becomes a
transformprocessor on the right contexts (resource, span, span event, log, data point), afilterprocessor that keeps failing probes,$$-escaped YAML and the pipeline order to use. - Lint OTTL you already have. Paste statements or a whole Collector config: syntax errors with line/column, unknown functions and wrong arity, paths that don't exist in the context, RE2-incompatible regexes, unescaped
$, ordering bugs (read after delete), always-true filters, legacy filter config,error_moderisks and version incompatibilities. - See the effect before deploying. The simulator runs the statements on your sample telemetry (or bundled realistic samples) and returns per-item diffs, per-statement hit counts, byte-size reduction and danger findings: filters that drop too much,
service.nameor trace context removed, dead rules, empty bodies, runtime errors. - Honest about limits. Phrases it doesn't understand are listed with the closest supported intents, functions it can't simulate are reported as not simulated — nothing is guessed or faked.
Who it's for
- Platform and observability engineers writing redaction, cost-reduction and normalisation rules for a shared Collector fleet.
- Teams cutting telemetry bills who want to know how much a filter drops before rolling it out.
- AI coding agents and MCP clients that edit Collector configs: generate, validate and simulate in a loop through one cheap HTTP call.
- CI pipelines that lint every change to
otel-collector.yaml(failOnSeverity, SARIF) — a fast pre-check beforeotelcol validate.
How to use
In Apify Console
- Write what you want in Requirements (the prefill is a working example), and/or paste OTTL into Statements or a full Collector config.
- Optionally paste Sample telemetry (OTLP/JSON) — otherwise the bundled samples (HTTP/DB/health-check spans, logs with PII, metrics) are used.
- Set your Collector version so the syntax matches your release, then click Start. The Output tab has four views: Overview, Generated config, Findings and Simulation.
Via the API (curl)
curl -s -X POST \"https://api.apify.com/v2/acts/michiganpublictech~ottl-studio/run-sync-get-dataset-items?token=$APIFY_TOKEN" \-H "Content-Type: application/json" \-d '{"requirements": "Redact emails in logs; drop debug logs; rename `user_id` to `user.id`","collectorVersion": "0.150.0"}' \| jq -r '.[0].data.generated.collector_yaml'
Python (apify-client)
from apify_client import ApifyClientclient = ApifyClient("<APIFY_TOKEN>")run = client.actor("michiganpublictech/ottl-studio").call(run_input={"mode": "validate","collectorConfig": open("otel-collector.yaml").read(),"failOnSeverity": "high",})for report in client.dataset(run["defaultDatasetId"]).iterate_items():for f in report["findings"]:print(f["severity"], f["rule_id"], f["location"], f["title"])
CI: lint the Collector config on every change
- name: Lint OTTLrun: |jq -n --rawfile cfg otel-collector.yaml '{mode: "all", collectorConfig: $cfg, failOnSeverity: "high"}' > input.jsoncurl -sf -X POST "https://api.apify.com/v2/acts/michiganpublictech~ottl-studio/runs?token=${{ secrets.APIFY_TOKEN }}&waitForFinish=300" \-H "Content-Type: application/json" -d @input.json | jq -e '.data.status == "SUCCEEDED"'
With failOnSeverity the run fails when any finding (lint or simulation) is at or above that severity. REPORT.sarif in the run's key-value store can be uploaded to GitHub code scanning.
Standby mode: HTTP API for AI agents and MCP clients
The Actor supports Actor Standby: a warm instance behind an HTTPS endpoint answers in milliseconds with no container start-up. Charging is identical to batch runs, and nothing is written to a dataset — the report comes back in the HTTP response.
BASE=https://michiganpublictech--ottl-studio.apify.actorAUTH="Authorization: Bearer $APIFY_TOKEN"# Free: capabilities, intent catalog, function catalog, rule catalogcurl -s "$BASE/" -H "$AUTH"curl -s "$BASE/intents" -H "$AUTH"curl -s "$BASE/functions?verbose=1" -H "$AUTH"curl -s "$BASE/rules" -H "$AUTH"# Generate (no simulation → only transform-generated is charged)curl -s -X POST "$BASE/generate" -H "$AUTH" -H "Content-Type: application/json" \-d '{"requirements": "Hash user IDs with SHA256 and drop spans for /internal/*", "collectorVersion": "0.110.0"}'# Validate your own OTTLcurl -s -X POST "$BASE/validate" -H "$AUTH" -H "Content-Type: application/json" \-d '{"statementContext": "span", "statements": ["delete_key(attributes, \"user.email\")","set(attributes[\"email_domain\"], Split(attributes[\"user.email\"], \"@\")[1])"]}'# → OTTL-SEM-004: user.email is read after an earlier statement deleted it# Simulate on your telemetry (statements, a collectorConfig or requirements)curl -s -X POST "$BASE/simulate" -H "$AUTH" -H "Content-Type: application/json" \-d '{"requirements": "Drop successful health-check spans","sampleTelemetry": {"spans": [{"name": "GET /healthz", "attributes": {"http.route": "/healthz"}},{"name": "GET /healthz", "status": {"code": "error"}, "attributes": {"http.route": "/healthz"}}]}}'
Endpoints: GET / (help; the platform readiness probe gets a plain ok), GET /intents, GET /functions, GET /rules, POST /generate, POST /validate, POST /simulate, POST /run (alias POST /; full Actor input, mode defaults to all). Options set on the Standby run's input (collectorVersion, syntaxStyle, processorName, signals, errorMode, …) act as defaults; the request body overrides them. Bodies are limited to 4 MB. Invalid input returns 400; a request that cannot be paid for (spending limit) returns 402; requests whose requirements are not understood return status: "failed" and are free.
From an MCP client
The Apify MCP server exposes the Actor as a tool (input schema = tool definition):
{"mcpServers": {"ottl-studio": {"url": "https://mcp.apify.com?tools=michiganpublictech/ottl-studio","headers": { "Authorization": "Bearer YOUR_APIFY_TOKEN" }}}}
Suggested agent instruction: "Before editing transform/filter processors in the Collector config, describe the change to the ottl-studio tool (or send the edited config with mode=all). Fix every high/critical finding and check data.simulation.stats for unexpected drops before committing." Agents that can make plain HTTP calls can use the Standby endpoints directly for the lowest latency.
Supported intents
61 deterministic intents. Each understands variations of the phrasing (synonyms, quoted or dotted attribute names, aliases such as "pod UIDs" → k8s.pod.uid, signal scoping such as "in logs", numbers, routes, /regex/). Several requests can be combined in one sentence, separated by commas, and, ;, new lines or bullet points. GET /intents returns the same list with more examples.
| Category | Intent | What it does | Processor | Example phrasing |
|---|---|---|---|---|
| redaction | drop_sensitive_keys | Delete secret-looking attributes | transform | Remove sensitive attributes like passwords and API keys |
| redaction | drop_stack_traces | Remove exception stack traces | transform | Drop exception stack traces |
| redaction | hash_attributes | Hash attribute values (SHA256) | transform | Hash user IDs with SHA256 |
| redaction | mask_attribute_value | Mask an attribute's value | transform | Mask the value of user.email |
| redaction | redact_bearer_tokens | Redact bearer tokens | transform | Redact bearer tokens |
| redaction | redact_credit_cards | Redact credit-card numbers | transform | Redact credit card numbers |
| redaction | redact_emails | Redact e-mail addresses | transform | Redact user email addresses |
| redaction | redact_ip_addresses | Redact IPv4 addresses | transform | Mask client IP addresses |
| redaction | redact_jwts | Redact JWTs | transform | Redact JWTs |
| redaction | redact_phone_numbers | Redact phone numbers | transform | Redact phone numbers |
| redaction | redact_ssns | Redact US social security numbers | transform | Redact SSNs |
| redaction | strip_query_strings | Strip URL query strings | transform | Strip query strings from url.full |
| redaction | strip_url_credentials | Strip credentials embedded in URLs | transform | Remove passwords embedded in URLs |
| attributes | add_environment | Tag telemetry with the deployment environment | transform | Add environment=production to everything |
| attributes | copy_attribute | Copy an attribute to a new key | transform | Copy http.route to route.template |
| attributes | copy_resource_attribute | Copy resource attributes onto items | transform | Copy resource attribute service.version to span attributes |
| attributes | default_service_name | Default service.name when missing | transform | Default service.name to "legacy-batch" when missing |
| attributes | delete_attributes | Delete attributes | transform | Remove Kubernetes pod UIDs |
| attributes | delete_matching_attributes | Delete attributes matching a pattern | transform | Remove all attributes starting with "http.request.header." |
| attributes | keep_matching_attributes | Keep only attributes matching a pattern | transform | Keep only attributes starting with "http." |
| attributes | keep_only_attributes | Keep only listed attributes | transform | Keep only http.route and http.response.status_code on spans |
| attributes | migrate_semconv | Migrate legacy semantic conventions | transform | Migrate legacy HTTP semantic conventions to the stable names |
| attributes | rename_attribute | Rename an attribute | transform | Rename user_id to user.id |
| attributes | set_attribute | Set an attribute to a static value | transform | Set team = "payments" on all spans |
| attributes | set_attribute_from_env | Set an attribute from an environment variable | transform | Set k8s.cluster.name from env var CLUSTER_NAME |
| attributes | set_attribute_if_missing | Set an attribute only when missing | transform | Add team = "payments" if missing |
| size | limit_attribute_count | Limit the number of attributes | transform | Limit spans to 64 attributes |
| size | truncate_attribute_values | Truncate attribute values | transform | Truncate attribute values to 256 characters |
| size | truncate_log_body | Truncate log bodies | transform | Truncate log bodies to 1024 characters |
| normalization | flatten_attributes | Flatten nested maps | transform | Flatten nested attributes |
| normalization | lowercase_attribute | Lowercase attribute values | transform | Lowercase http.request.method |
| normalization | normalize_http_route | Normalize http.route | transform | Normalize http.route, replacing numeric IDs with {id} |
| normalization | normalize_span_names | Template IDs in span names | transform | Replace IDs and UUIDs in span names with placeholders |
| normalization | uppercase_attribute | Uppercase attribute values | transform | Uppercase http.request.method |
| parsing | extract_with_regex | Extract fields with a regex | transform | Extract fields from the log body using regex /user=(?P |
| parsing | parse_json_body | Parse JSON log bodies into attributes | transform | Parse JSON log bodies into attributes |
| parsing | parse_key_value_body | Parse key=value log bodies | transform | Parse key=value pairs from log bodies into attributes |
| parsing | severity_from_body | Derive log severity from the body text | transform | Set log severity from the body text |
| filtering | drop_by_namespace | Drop telemetry from a Kubernetes namespace | filter | Drop everything from namespace kube-system |
| filtering | drop_by_service | Drop telemetry from a service | filter | Drop all telemetry from service "load-generator" |
| filtering | drop_datapoints_by_attribute | Drop metric data points by attribute | filter | Drop datapoints where http.route = "/healthz" |
| filtering | drop_debug_logs | Drop DEBUG/TRACE logs | filter | Drop debug logs |
| filtering | drop_health_checks | Drop health-check spans | filter | Drop health check spans |
| filtering | drop_logs_below_severity | Drop logs below a severity | filter | Drop logs below WARN |
| filtering | drop_logs_matching | Drop logs whose body matches text | filter | Drop logs containing "connection reset by peer" |
| filtering | drop_metrics_by_name | Drop metrics by name | filter | Drop the metric process.runtime.cpython.gc_count |
| filtering | drop_metrics_by_prefix | Drop metrics by name prefix/regex | filter | Drop metrics starting with "debug." |
| filtering | drop_short_spans | Drop very short spans | filter | Drop spans shorter than 1ms |
| filtering | drop_span_events_by_name | Drop span events by name | filter | Drop span events named "message" |
| filtering | drop_spans_by_attribute | Drop spans by attribute value | filter | Drop spans where db.system = "redis" |
| filtering | drop_spans_by_kind | Drop spans of a kind | filter | Drop internal spans |
| filtering | drop_spans_by_name | Drop spans by name | filter | Drop spans named "GET /favicon.ico" |
| filtering | drop_spans_by_route | Drop spans for a route | filter | Drop spans for /internal/* |
| filtering | drop_successful_health_checks | Drop successful health-check spans | filter | Drop successful health-check spans |
| filtering | keep_only_error_logs | Keep only ERROR+ logs | filter | Keep only error logs |
| filtering | keep_only_error_spans | Keep only error spans | filter | Keep only error spans |
| traces | span_status_from_http | Set span status ERROR from HTTP status | transform | Mark spans with HTTP 5xx responses as errors |
| metrics | convert_gauge_to_sum | Convert gauges to sums | transform | Convert gauge jobs.done to a sum |
| metrics | convert_sum_to_gauge | Convert sums to gauges | transform | Convert sum queue.depth to a gauge |
| metrics | rename_metric | Rename a metric | transform | Rename metric old.name to new.name |
| metrics | scale_metric_unit | Convert a metric's unit | transform | Convert db.client.latency from ms to s |
Something missing? Phrases that match nothing come back under data.unrecognized with the three closest intents; phrases that match an intent but lack a required detail (e.g. "rename the attribute" without names) say what is missing.
Collector versions and syntax style
| Collector | Generated syntax | Notes |
|---|---|---|
| < 0.120 (default target 0.110.0) | Grouped: trace_statements: [{context: span, statements: [...]}] | Works on every OTTL-capable release. |
| ≥ 0.120 | Flat: trace_statements: ['set(span.attributes["x"], ...)'] with context-prefixed paths (resource., span., spanevent., log., metric., datapoint.) | Context inferred per statement. Override with syntaxStyle. |
Filter conditions are always emitted in the classic per-context blocks (traces.span, logs.log_record, metrics.datapoint, …), which all OTTL-capable Collectors accept. Regex $ is written as $$ so Collector environment expansion leaves it alone.
Verified with real Collectors: the output of every intent example (all 61 intents, 96 examples) was checked with otelcol-contrib validate — grouped style on 0.115, 0.148 and 0.150.1, flat style on 0.148 and 0.150.1. Flat transform lists are rejected by 0.115 ("expected a map, got 'string'"), which is why older versions get the grouped style. The 0.120 cut-over and the other entries of the bundled version table (error_mode ~0.71, silent ~0.95, named arguments ~0.100, function since hints) are best-effort and labelled as such in the output; confirm against the transformprocessor README of your exact release.
Validation rules
66 rules: 56 lint rules and 10 simulation rules. Full catalog with recommendations: RULES.md.
| Family | Rules | What they catch |
|---|---|---|
OTTL-SYN syntax | 4 | Statements that don't parse (with line:column and hints for 'single quotes', &&, !), conditions where a statement is required and vice versa, empty statements |
OTTL-FN functions | 12 | Unknown functions, wrong argument count or type, converter used as a statement / editor inside an expression, non-writable targets, unknown named arguments, invalid options and enums, deprecated functions, functions that may need a newer Collector or are not available in the context |
OTTL-PATH paths | 8 | Unknown paths for the context, paths from another context, writes to read-only fields, attributes.key instead of attributes["key"], parent-context writes from a child context, context-prefixed paths on older Collectors, legacy semconv attributes in conditions, contexts that cannot be inferred |
OTTL-RE regex | 8 | Regexes that don't compile or use syntax RE2 lacks (look-around, backreferences), \1 instead of $1, ExtractPatterns without named groups, invalid Go string escapes ("\d"), unanchored or unescaped key patterns, redundant .* |
OTTL-SEM semantics & ordering | 13 | Read after delete, values overwritten before use, deleting service.name, unconditional statements and overwrites, copies that keep the original, duplicates, constant where clauses, typed fields compared with or set to the wrong type, non-positive limits, keep_keys with an empty list, body parsers without a type guard |
OTTL-CFG configuration | 11 | error_mode: propagate / invalid error_mode, processors not used in any pipeline or without statements for its signal, transform/filter after batch, always-true (drops everything) and always-false filters, invalid statement groups, groups under the wrong signal, unescaped $, legacy include/exclude filters |
OTTL-SIM simulation | 10 | Filters dropping at least dropWarningPercent of items, service.name or trace context removed, overwritten values, empty bodies or span names, dead rules, patterns that changed every item, runtime errors, read-after-delete observed at runtime, statements that could not be simulated, filters that dropped nothing |
Simulation
Statements run in pipeline order (from your collectorConfig, otherwise filter → transform, or transform → filter when the filter reads fields the transform writes) on a copy of the telemetry, following the Collector's semantics:
- Transform: statements per context in order;
whereper statement; group-levelconditions(OR);nilvalues makeseta no-op; resource/scope statements apply once per resource/scope. - Filter: conditions are OR-ed; matching spans, span events, log records, metrics or data points are dropped, and metrics left without data points are removed — as the filter processor does.
error_mode:ignore/silentskip the failing statement for that item;propagaterejects the signal's whole batch (reported as a finding).- Output:
data.simulationholds per-signalitems_in/out/dropped, estimated bytes before/after and reduction %, attributes added/removed, values changed, up tomaxDiffItemsitem diffs (removed,added,changedwith before/after,dropped), and per-statement stats (evaluated,matched,changed,errors).
Simulated (48 functions): editors set, delete_key, delete_matching_keys, keep_keys, keep_matching_keys, limit, merge_maps, replace_pattern, replace_all_patterns, replace_match, replace_all_matches, truncate_all, flatten, append, convert_sum_to_gauge, convert_gauge_to_sum, scale_metric; converters Concat, ConvertCase, Double, ExtractPatterns, FNV, Format, Hex, Int, IsBool, IsDouble, IsInt, IsList, IsMap, IsMatch, IsRootSpan, IsString, Len, Log, MD5, ParseJSON, ParseKeyValue, SHA1, SHA256, SHA512, Sort, SpanID, Split, String, Substring, TraceID, UUID.
Not simulated (validated, but reported as not simulated when executed — OTTL-SIM-009): time and duration functions (Time, Now, Duration, FormatTime, TruncateTime, Unix*, Hour…Weekday, Hours…Nanoseconds), URL, UserAgent, ParseCSV, ParseXML, ParseSimplifiedXML, Decode, Base64Decode, ExtractGrokPatterns, SliceToMap, ToKeyValueString, extract_sum_metric, extract_count_metric, copy_metric, aggregate_on_attributes, convert_summary_*, routing connector route() execution, trace_state keys, scale_metric on histograms/summaries.
Known approximations: limit keeps keys in insertion order (Go map order is unspecified); truncate_all and Substring count characters (Go counts bytes, which differs for non-ASCII); Concat with nil and ConvertCase(…, "camel") edge cases.
Input
| Field | Type | Default | Description |
|---|---|---|---|
mode | string | all | generate (English → OTTL), validate (lint OTTL/config), simulate (run on telemetry), all. |
requirements | string or array | — | Plain-English requests (max 20,000 characters). |
statements | array | — | Your OTTL: strings, or {"context": "span", "statements": [...]} / {"context": "log", "conditions": [...]} objects (max 500). |
statementContext | string | auto | Context for plain string statements; auto infers it from the paths. |
statementType | string | transform | transform (editor statements) or filter (boolean conditions). |
collectorConfig | string | — | Full Collector YAML (max 2 MiB): transform/filter processors and routing connector statements are extracted, linted and simulated in pipeline order. |
sampleTelemetry | object | bundled | OTLP/JSON (resourceSpans / resourceLogs / resourceMetrics, camelCase or snake_case) or simplified {"resource": {...}, "spans": [], "logs": [], "metrics": []}. Up to 2,000 items per signal are simulated. |
requests | array | — | Several requests (objects with the fields above plus name); one dataset item and charge each. |
collectorVersion | string | 0.110.0 | Your Collector version; picks the syntax style and version checks. |
syntaxStyle | string | auto | auto, grouped or flat. |
processorName | string | generated | Generated processors are transform/<name> and filter/<name>. |
signals | array | all | Signals that requirements apply to when they don't name one. |
errorMode | string | ignore | error_mode of generated processors and default for simulation. |
useBundledSamples | boolean | true | Simulate on bundled telemetry when sampleTelemetry is empty. |
dropWarningPercent | integer | 50 | OTTL-SIM-001 threshold. |
maxDiffItems | integer | 25 | Maximum item diffs in the output (0–500). |
failOnSeverity | string | none | CI gate: fail the run when a finding is at or above this severity. |
disabledRules | array | — | Rule IDs to skip (custom-rules feature). |
licenseKey | string (secret) | — | Enterprise / prepaid license. |
Output
One dataset item per request, in the same Report format as every Michigan Public Technology Actor (shortened):
{"product": "ottl-studio","target": "request","status": "ok","score": 100,"summary": {"critical": 0, "high": 0, "medium": 0, "low": 0, "info": 4},"findings": [{"rule_id": "OTTL-SEM-001", "severity": "info","location": "processors.transform/generated.log_statements[1].statements[0]", "…": "…"}],"data": {"mode": "all","collector_version": "0.110.0","syntax_style": {"style": "grouped", "reason": "classic `context:` groups work on every Collector release with OTTL"},"intents": [{"intent": "delete_attributes", "phrase": "Remove Kubernetes pod UIDs", "confidence": 0.85, "…": "…"}],"unrecognized": [],"generated": {"collector_yaml": "processors:\n filter/generated:\n error_mode: ignore\n traces:\n span:\n - '(IsMatch(attributes[\"http.route\"], \"^/(health|healthz|…)/?$$\") …) and status.code != STATUS_CODE_ERROR'\n transform/generated:\n error_mode: ignore\n trace_statements:\n - context: resource\n statements:\n - 'delete_key(attributes, \"k8s.pod.uid\")'\n …","pipeline_snippet": "service:\n pipelines:\n traces:\n processors: [memory_limiter, filter/generated, transform/generated, batch]\n …","statements": [{"signal": "traces", "context": "resource", "statement": "delete_key(attributes, \"k8s.pod.uid\")", "intent": "delete_attributes"}],"revalidation_findings": []},"simulation": {"telemetry_source": "bundled","processor_order": {"traces": ["filter/generated", "transform/generated"]},"stats": {"signals": {"traces": {"items_in": 10, "items_out": 8, "items_dropped": 2, "bytes_reduction_pct": 16.3}},"attributes_removed": 21, "values_changed": 10, "bytes_reduction_pct": 10.9},"diffs": [{"item": "checkout · span 'GET /healthz'", "dropped": true},{"item": "checkout · span 'SELECT shop.orders'", "removed": ["resource.attributes[\"k8s.pod.uid\"]"],"changed": {"attributes[\"db.statement\"]": {"before": "… 'jane.doe@example.com' …", "after": "… '[REDACTED_EMAIL]' …"}}}]},"warnings": [],"version_notes": {"disclaimer": "…", "notes": [{"version": "~0.120", "component": "transform", "note": "…"}]}}}
validate requests add data.validation (statements with their inferred contexts, processors and pipelines from your config). The key-value store gets OUTPUT (run summary), REPORT.md and REPORT.sarif.
Pricing
Pay per event — you only pay for successful work:
| Event | Price | When |
|---|---|---|
transform-generated | $0.10 per request | OTTL was generated from requirements and/or your statements / config were validated (batch run or any Standby POST). |
simulation-run | $0.05 per request | In addition, only when a simulation actually ran (mode simulate or all). |
Free: requests where nothing was recognised, unparsable YAML/JSON, simulate-only requests with unusable telemetry, invalid input, GET /, /intents, /functions, /rules. Example: 20 generate-and-simulate requests ≈ 20 × $0.15 = $3. Set a maximum cost per run in Apify: the Actor checks the remaining budget before each request and skips the simulation (with a warning) when only the generation is affordable, so you are never charged beyond it. Standard Apify platform usage applies.
Limits and safety
- Sizes: requirements 20,000 characters, 500 statements, Collector config 2 MiB, Standby bodies 4 MB, 2,000 simulated items per signal per request, up to 100
requestsper batch run on the Store plan. - No network, no LLM: the Actor makes no outbound requests; everything runs from bundled catalogs.
- Your data stays in your run: telemetry and configs are processed in memory in your own Apify run (or the warm Standby instance serving your request); results go only to your dataset / key-value store or the HTTP response.
- Generated configs are a starting point. Review them and run
otelcol validatewith your real Collector build before deploying.
Plans and licensing
Store users need nothing beyond an Apify account: pay-per-event pricing applies, and SARIF plus the CI gate are included. Disabling rules (disabledRules) and enterprise/prepaid options are described in ../../docs/licensing.md. Local runs without a license use the evaluation plan (3 requests per run).
Limitations
- Intent recognition is pattern-based, not an LLM: it covers the 61 intents above and their common phrasings. Complex or conditional requests ("only for service X on weekdays") may need hand-written OTTL — which you can then validate and simulate here.
- The simulator models a subset of OTTL (see Simulation); unsupported functions are reported, never faked. Time-based logic, grok, URL/User-Agent parsing and metric-creating functions are not executed.
- Version knowledge is a bundled, best-effort table. Function
sinceversions are approximate; the grouped/flat syntax split was verified on the Collector builds listed above. - Lint locations are YAML paths plus statement-relative line:column, not line numbers in your YAML file.
- Routing connector statements are extracted and linted but not executed.
- Per-signal pipeline wiring of a supplied config is checked for missing processors, not for every possible misconfiguration.
FAQ
Does it need my Collector or an LLM? No. Everything is deterministic and offline; the same input always gives the same output.
Which syntax should I use? Leave syntaxStyle on auto and set collectorVersion. Flat statements are shorter and easier to read on 0.120+; grouped statements work everywhere.
Why does my regex need $$? The Collector expands ${env:VAR} and $VAR in config files, so a literal $ in a regex anchor must be written $$. Generated YAML already does this, and OTTL-CFG-007 flags it in yours.
Can I simulate my own telemetry? Yes — paste OTLP/JSON (e.g. from the Collector file exporter or OTEL_EXPORTER_OTLP_PROTOCOL=http/json) or the simplified shape into sampleTelemetry.
What if a requirement isn't understood? It is listed under data.unrecognized with the closest intents and example phrasings. If nothing at all was recognised, the request is marked failed and is free.
What happens when my spending limit is reached? The Actor stops before the next paid request (or skips the simulation if only generation is affordable); results already produced are kept.
Changelog
See CHANGELOG.md.
