mcpField
O
oodle-log-metrics

oodle-log-metrics

github.com/oodle-aiCloud & DevOps0125 installsUpdated 3w ago
Repository

About oodle-log-metrics

oodle-log-metrics is a skill that guides agents in defining Oodle rules to turn log lines into metrics. It focuses on validating filters, controlling label cardinality, and preserving existing rules when updating them.

Key features

  1. 01Validate log filters before rule creation
  2. 02Estimate label cardinality
  3. 03Define count, counter, gauge, and histogram metrics
  4. 04Extract labels and values with regex
  5. 05Retrieve rules before updating

Use cases

  1. 01Count matching log lines as metrics
  2. 02Create alerts from log events
  3. 03Measure tool latency from plain-text logs

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/oodle-ai/agent-skills --skill oodle-log-metrics

After installing, type/oodle-log-metricsin Claude Code to confirm it loaded.

Details

From the project README

Oodle Log Metrics — Rules and Cardinality

This skill teaches the agent to convert log streams into metrics safely: validate the filter, narrow the labels, and avoid creating high-cardinality time series. A log metrics rule is also the way to count or alert on values inside log lines: a monitor alerts on the metric that the rule emits.

Prerequisites

brew install oodle-ai/oodle/oodle
oodle configure

Confirm the log-metrics endpoint works:

oodle log-metrics list -o json | jq 'length'

Command Execution Order

Before running any oodle command:

  1. Check whether the rule's filter and labels are already in context.
  2. If not, run a sample log query (e.g. oodle logs query, see the oodle-logs skill, or the log explorer) to verify the filter actually matches logs.
  3. Estimate cardinality: the distinct values of each label, multiplied together.
  4. Run oodle log-metrics create -f rule.json only after the filter is validated.
  5. Do not run update without first running get to capture the existing rule.

Quick Reference

TaskCommand
List rulesoodle log-metrics list -o json
Get ruleoodle log-metrics get <id> -o json
Create ruleoodle log-metrics create -f rule.json
Update ruleoodle log-metrics update <id> -f rule.json
Delete ruleoodle log-metrics delete <id> --force

Common Operations

Rule schema
{
  "name": "http_errors_from_logs",
  "filter": {
    "all": [
      {"field": "level", "operator": "is", "value": "error"},
      {"field": "service", "operator": "is", "value": "api"}
    ]
  },
  "labels": [
    {"name": "service", "valueExtractor": {"field": "service"}},
    {"name": "env", "valueExtractor": {"field": "env"}}
  ],
  "metricDefinitions": [
    {"name": "http_errors", "type": "log_count"}
  ]
}

Field meaning:

FieldMeaning
nameHuman identifier for the rule. Required.
filterWhich log lines the rule reads. A condition {"field", "operator", "value", "jsonPath"}, or {"all": [...]} (AND), {"any": [...]} (OR), {"not": <filter>}. You can nest them. Operators: is, contains, matches regex, exists. There is no !=; use not.
labelsLabels added to the emitted metrics. Each has a name and a static value, or a valueExtractor {"field", "jsonPath", "regex"}.
metricDefinitionsThe metrics to emit. At least one. Each has a name and a type: log_count (number of matching lines), counter (sum of a value), gauge (last value), or histogram (distribution of a value). counter, gauge, and histogram read the value from field, with an optional jsonPath or regex.
  • field is a top-level log field: letters, digits, and underscores only. Read a nested JSON value with jsonPath, for example {"field": "log", "jsonPath": "user.id"}.
  • A regex uses Rust syntax. Capture group 1 is the value, so put one group around the part you need. In JSON, escape each backslash: \d is "\\d".
  • When the regex does not match a line, the label is empty, and a value is 0. So make the filter select only the lines that contain the value.
  • The emitted metric name is oodle_logs_<name> (oodle_logs_http_errors). A histogram emits _bucket, _sum, and _count series. Query log_count and counter with increase(...) or rate(...), and aggregate with sum by (...).
  • A rule applies only to logs that arrive after you create it. For past data, use oodle logs query.
  • Put a single condition inside an all list, as in the examples. That form works with every CLI and server version.
Creating a rule
# ✅ CORRECT — validate filter first, then create
# (preview with the log explorer or `oodle logs query`)
oodle log-metrics create -f rule.json

# ❌ WRONG — creating with an untested filter; the resulting metric is silently empty
oodle log-metrics create -f <(echo '{"name":"x","filter":{"all":[{"field":"levl","operator":"is","value":"eror"}]},"metricDefinitions":[{"name":"x","type":"log_count"}]}')
Updating a rule
# ✅ CORRECT — get → edit → update
oodle log-metrics get lm_123 -o json > rule.json
jq '.labels = [{"name":"service","valueExtractor":{"field":"service"}},{"name":"env","valueExtractor":{"field":"env"}}]' rule.json > rule.new.json
oodle log-metrics update lm_123 -f rule.new.json

# ❌ WRONG — partial payload removes existing fields (and has no `id`, so it is rejected)
oodle log-metrics update lm_123 -f <(echo '{"labels":[{"name":"service","valueExtractor":{"field":"service"}}]}')
Deleting a rule
# ✅ CORRECT — verify and delete
oodle log-metrics get lm_123 -o json > /dev/null
oodle log-metrics delete lm_123 --force

# ❌ WRONG — speculative delete by name match
oodle log-metrics delete "$(oodle log-metrics list | grep errors | awk '{print $1}')" --force
Alerting on log lines

A monitor runs PromQL, not a log query. To alert on log lines, create a log metrics rule, then a monitor on the metric it emits (see the oodle-monitors skill). A rule reads the raw line text with a regex, so this works for plain-text lines, and you do not need a log transform. A label extracted from the line (user, tool, tenant) names the series in the alert.

Example: one alert per user whose credit ran out, from lines like:

2026-10-07T10:12:03Z WARN credit exhausted for user=alice plan=pro

credit-exhausted.json:

{
  "name": "credit-exhausted",
  "filter": {"all": [{"field": "message", "operator": "contains", "value": "credit exhausted"}]},
  "labels": [
    {"name": "user", "valueExtractor": {"field": "message", "regex": "user=(\\S+)"}}
  ],
  "metricDefinitions": [{"name": "credit_exhausted", "type": "log_count"}]
}

credit-exhausted-monitor.json (op 2 is >):

{
  "name": "Credit exhausted",
  "promql_query": "sum by (user) (increase(oodle_logs_credit_exhausted[10m]))",
  "conditions": {"critical": {"op": 2, "value": 0}},
  "labels": {"team": "billing", "env": "prod"},
  "annotations": {"summary": "User {{ $labels.user }} ran out of credit"}
}
oodle log-metrics create -f credit-exhausted.json
oodle monitors create -f credit-exhausted-monitor.json
# Later: whose credit ran out in the last 24 hours
oodle metrics query --query 'sum by (user) (increase(oodle_logs_credit_exhausted[24h])) > 0' -o json

A label per user is fine here because the event is rare (see "Keep labels to 2–3 low-cardinality labels").

Example: a value inside the line, such as the duration in tool Bash ok in 12345 ms. Use one regex per value, each with one capture group, and a filter that requires the value:

{
  "name": "tool-latency",
  "filter": {"all": [{"field": "message", "operator": "matches regex", "value": "tool \\S+ (?:ok|failed) in \\d+ ms"}]},
  "labels": [
    {"name": "tool", "valueExtractor": {"field": "message", "regex": "tool (\\S+) "}},
    {"name": "status", "valueExtractor": {"field": "message", "regex": " (ok|failed) in "}}
  ],
  "metricDefinitions": [
    {"name": "tool_calls", "type": "log_count"},
    {"name": "tool_duration_ms", "type": "histogram", "field": "message", "regex": " in (\\d+) ms"}
  ]
}
# p95 tool latency in ms, per tool
histogram_quantile(0.95, sum by (le, tool) (rate(oodle_logs_tool_duration_ms_bucket[5m])))
# failed calls per tool
sum by (tool) (increase(oodle_logs_tool_calls{status="failed"}[10m]))

Best Practices

Validate the filter against real log volume before creating the rule

A typo in the filter (levl=eror) creates a rule that silently emits zero data points.

# ✅ CORRECT — preview matching log volume in the log explorer first;
# only then run `oodle log-metrics create`
# (or run `oodle logs query` with the same condition and confirm count > 0)

# ❌ WRONG — create the rule, then notice the dashboard panel is empty next week
oodle log-metrics create -f rule.json
Keep labels to 2–3 low-cardinality labels

Each label multiplies the time-series count. Avoid request_id, user_id, trace_id, path (with IDs). A user label is fine only on a rare event, such as credit refusals.

# ✅ CORRECT — bounded labels
"labels": [{"name": "service", "valueExtractor": {"field": "service"}}, {"name": "env", "valueExtractor": {"field": "env"}}]

# ❌ WRONG — `user_id` on every request blows up cardinality (one series per user)
"labels": [{"name": "service", "valueExtractor": {"field": "service"}}, {"name": "user_id", "valueExtractor": {"field": "user_id"}}]
Always get before update to preserve fields

update replaces the document. Sending a single field nulls everything else.

# ✅ CORRECT
oodle log-metrics get lm_123 -o json > rule.json
jq '.filter.all += [{"field": "status", "operator": "matches regex", "value": "^5"}]' rule.json > rule.new.json
oodle log-metrics update lm_123 -f rule.new.json

# ❌ WRONG — clobbers labels and metricDefinitions
oodle log-metrics update lm_123 -f <(echo '{"filter":{"all":[{"field":"level","operator":"is","value":"error"}]}}')
Use a stable, descriptive metric name

The emitted name is oodle_logs_<name>. A <domain>_<measurement> name makes the metric easy to find in dashboards and drop rules. Use only letters, digits, and underscores.

# ✅ CORRECT
"metricDefinitions": [{"name": "http_errors", "type": "log_count"}]

# ❌ WRONG — generic name collides with other rules
"metricDefinitions": [{"name": "errors", "type": "log_count"}]

Failure Handling

ErrorCauseFix
401 UnauthorizedInvalid or missing API keyRun oodle configure or set OODLE_API_KEY
404 Not FoundLog-metric rule ID does not existVerify with oodle log-metrics list -o json
connection refusedWrong OODLE_DEPLOYMENT URLCheck OODLE_DEPLOYMENT env var
400 Match must have a valid OperatorOperator is not is, contains, matches regex, or existsUse one of the four; for "not equal", wrap the condition in not
400 Field ... must contain only alphanumeric characters and underscoresDotted field name in fieldUse the top-level field and put the path in jsonPath
400 Regex ... is not a valid rust regexRegex syntax error, or a JSON escape is missingEscape backslashes in JSON (\\d); no look-around
400 'logCount' metric definition has a Field, JSONPath, or Regexlog_count has field, jsonPath, or regexRemove them
400 Log Metrics ID in path and body don't matchBody id is missing or differs from <id>Run get and edit that output
Metric exists but has no data pointsFilter doesn't match any logs, or no new lines since the rule was createdRe-test the filter in the log explorer or with oodle logs query; check field names match the log schema. Rules do not backfill
Label is empty / values are all 0The regex does not match, or group 1 is not a numberTest the regex on sample lines; make the filter require the value
Cardinality alarm in the UIlabels includes a high-cardinality fieldEdit the rule to drop that label; old series age out at the regular retention
429 Too Many RequestsBulk rule creationAdd --retries 3, throttle to <10 creates per second

References

Comments

Markdown supported