From lc-advanced-skills
Customizes and tests Grok parsing rules for LimaCharlie USP, Cloud Sensor, and External adapters. Generates patterns from sample logs, validates against test data, and deploys configurations for new log sources, troubleshooting, or field extraction changes.
npx claudepluginhub refractionpoint/lc-ai --plugin lc-advanced-skillsThis skill is limited to using the following tools:
A guided workflow for creating, testing, and deploying Grok parsing configurations for LimaCharlie adapters. This skill helps you customize how log data is parsed and normalized as it's ingested into LimaCharlie.
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Migrates code, prompts, and API calls from Claude Sonnet 4.0/4.5 or Opus 4.1 to Opus 4.5, updating model strings on Anthropic, AWS, GCP, Azure platforms.
Reviews prose for communication issues impeding comprehension, outputs minimal fixes in a three-column table per Microsoft Writing Style Guide. Useful for 'review prose' or 'improve prose' requests.
A guided workflow for creating, testing, and deploying Grok parsing configurations for LimaCharlie adapters. This skill helps you customize how log data is parsed and normalized as it's ingested into LimaCharlie.
Prerequisites: Run
/init-lcto initialize LimaCharlie context.
All LimaCharlie operations use the limacharlie CLI directly:
limacharlie <noun> <verb> --oid <oid> --output yaml [flags]
For command help and discovery: limacharlie <command> --ai-help
| Rule | Wrong | Right |
|---|---|---|
| CLI Access | Call MCP tools or spawn api-executor | Use Bash("limacharlie ...") directly |
| Output Format | --output json | --output yaml (more token-efficient) |
| Filter Output | Pipe to jq/yq | Use --filter JMESPATH to select fields |
| LCQL Queries | Write query syntax manually | Use limacharlie ai generate-query first |
| Timestamps | Calculate epoch values | Use date +%s or date -d '7 days ago' +%s |
| OID | Use org name | Use UUID (call limacharlie org list if needed) |
⚠️ TIMEZONE NOTE: Patterns like
SYSLOGTIMESTAMP(Dec 16 17:50:04) lack timezone info. LimaCharlie assumes UTC. This skill automatically detects timezone mismatches by comparing parsed times to current UTC.
Use this skill when:
Common scenarios:
This skill provides a 5-phase workflow:
| Type | Description | Configuration Method |
|---|---|---|
| External Adapter | Cloud-managed adapters (syslog, webhook, API) | Update via set_external_adapter API |
| Cloud Sensor | Virtual sensors for cloud platforms (AWS, Azure, GCP, SaaS) | Update via set_cloud_sensor API |
| One-off/USP Adapter | On-prem adapters with local configuration | Output YAML/CLI config for user |
Before starting, gather:
Get the list of available organizations:
limacharlie org list --output yaml
Use AskUserQuestion to let the user select an organization if multiple are available.
Ask the user which adapter type they're working with using AskUserQuestion:
Option A: External Adapter (cloud-managed syslog, webhook, API)
limacharlie external-adapter list --oid <SELECTED_ORG_ID> --output yaml
limacharlie external-adapter get --key <ADAPTER_NAME> --oid <SELECTED_ORG_ID> --output yaml
Option B: Cloud Sensor (AWS, Azure, GCP, SaaS)
limacharlie cloud-adapter list --oid <SELECTED_ORG_ID> --output yaml
limacharlie cloud-adapter get --key <SENSOR_NAME> --oid <SELECTED_ORG_ID> --output yaml
Option C: One-off/USP Adapter (local adapter, no cloud config)
No API calls needed. Proceed directly to sample log collection.
Option A: Query from adapter's sensor (for cloud-managed adapters)
If the adapter is already ingesting data, you can fetch sample events:
limacharlie sensor list --oid <SELECTED_ORG_ID> --filter "[?iid=='<IID>']" --output yaml
IMPORTANT: Always calculate timestamps dynamically using Bash FIRST:
# Run this to get current epoch timestamps for the last hour
start=$(date -d '1 hour ago' +%s) && end=$(date +%s) && echo "start=$start end=$end"
Then use the actual output values (e.g., start=1764805928 end=1764809528) directly in the CLI call - do NOT use placeholder values:
limacharlie search run --query "event sid = '<SENSOR_ID>'" --start $start --end $end --oid <SELECTED_ORG_ID> --filter "[:10]" --output yaml
Option B: User provides sample logs
If no data is available or for one-off adapters, ask the user to paste sample log lines.
Use AskUserQuestion or direct text input to collect sample logs.
When fetching events from a sensor, unparsed logs appear as:
event_type: "unknown_event" in the routingtext field with the raw log lineExample of an unparsed event:
{
"event": {
"text": "2025-12-03 16:41:19 status installed shared-mime-info:amd64 2.2-1"
},
"routing": {
"event_type": "unknown_event",
"hostname": "my-adapter"
}
}
This indicates no Grok parsing is configured for the adapter. The raw content in the text field is what you need to analyze and create a parsing pattern for.
Analyze the sample logs and generate an appropriate Grok pattern.
Common Grok Patterns:
| Pattern | Description | Example Match |
|---|---|---|
%{TIMESTAMP_ISO8601:timestamp} | ISO 8601 timestamp (YYYY-MM-DD) | 2024-01-15T12:30:45Z or 2024-01-15 12:30:45 |
%{DATESTAMP:timestamp} | US/EU date format (MM-DD-YY) | 01-15-24 12:30:45 |
%{SYSLOGTIMESTAMP:date} | Syslog timestamp | Jan 15 12:30:45 |
%{IP:ip_address} | IPv4/IPv6 address | 192.168.1.100 |
%{HOSTNAME:host} | Hostname | server01.example.com |
%{NUMBER:value} | Numeric value | 12345 |
%{INT:count} | Integer | 42 |
%{WORD:action} | Single word | ACCEPT |
%{DATA:field} | Non-greedy match (up to delimiter) | any text |
%{GREEDYDATA:message} | Greedy match (rest of line) | everything else |
%{LOGLEVEL:level} | Log level | ERROR, WARN, INFO |
%{POSINT:port} | Positive integer | 443 |
Timestamp Pattern Warning: Do NOT use
%{DATESTAMP}forYYYY-MM-DDformat logs - it will misparse the year (treating2025-12-03as day=25, month=12, year=03). Use%{TIMESTAMP_ISO8601}instead for anyYYYY-MM-DDformatted timestamps.
IMPORTANT - Grok Field Name for Text Platform:
For text platform adapters, always use message as the key in parsing_grok:
parsing_grok:
message: '%{PATTERN:field} ...' # Always use "message" as the key, NOT "text"
Building the Mapping Configuration:
client_options:
mapping:
parsing_grok:
message: '%{SYSLOGTIMESTAMP:date} %{HOSTNAME:host} %{WORD:service}\[%{INT:pid}\]: %{GREEDYDATA:message}'
event_type_path: "service"
event_time_path: "date"
sensor_hostname_path: "host"
REQUIRED: After validation, MUST perform this sanity check for patterns without explicit timezone.
Applies when Grok pattern uses:
%{SYSLOGTIMESTAMP} - e.g., Dec 16 17:50:04%{DATESTAMP} - e.g., 12-16-24 17:50:04%{TIMESTAMP_ISO8601} without Z or offset - e.g., 2024-12-16 17:50:04Automatic Detection Steps:
After validate_usp_mapping returns, extract the event_time from results (epoch milliseconds)
Get current UTC epoch:
date -u +%s
# Example: event_time=1734357004000 (milliseconds)
# current_utc=$(date -u +%s)
# offset_hours=$(( (current_utc - event_time/1000) / 3600 ))
If offset is 4-12 hours: The logs are likely in a local timezone (US timezones are UTC-5 to UTC-8)
Automatically warn the user:
⚠️ Timezone Warning: The parsed event_time appears to be ~[X] hours behind current UTC time. This suggests your log timestamps are in local time (e.g., Pacific Time), not UTC.
LimaCharlie interprets all timestamps without timezone info as UTC. Your events will appear [X] hours in the past.
Recommendation: Configure your log source to emit UTC timestamps:
- rsyslog: Add
$ActionFileDefaultTemplate RSYSLOG_FileFormatto rsyslog.conf- systemd-journald: Already uses UTC by default
Continue with deployment even if warning is shown (user can address later)
Step 1: Validate the parsing configuration
Use validate_usp_mapping to test the Grok pattern against sample data:
# Write adapter config with mapping and sample data to a YAML file:
cat > /tmp/usp-test.yaml << 'EOF'
parsing_grok:
message: "%{SYSLOGTIMESTAMP:date} %{HOSTNAME:host} %{WORD:service}\\[%{INT:pid}\\]: %{GREEDYDATA:msg}"
event_type_path: service
event_time_path: date
sensor_hostname_path: host
sample_data:
- "<SAMPLE_LOG_LINES>"
EOF
limacharlie usp validate --oid <SELECTED_ORG_ID> --platform text --input-file /tmp/usp-test.yaml --output yaml
Note: The
mappingobject should haveparsing_grok,event_type_path,event_time_path, andsensor_hostname_pathat the same level - NOT nested under aparsingkey.
Step 2: Review parsed results
Show the parsed events to the user:
Automatic Timezone Sanity Check:
If the Grok pattern uses SYSLOGTIMESTAMP, DATESTAMP, or TIMESTAMP_ISO8601 without explicit timezone:
Get current UTC: current_utc=$(date -u +%s)
Extract event_time from validation result (milliseconds)
Calculate offset: offset_hours=$(( (current_utc - event_time/1000) / 3600 ))
If offset is 4-12 hours, display warning:
"⚠️ Timezone Mismatch Detected: Parsed time is ~[offset] hours behind UTC. Your logs appear to be in local time. Events will be timestamped incorrectly in LimaCharlie. Consider configuring your log source to use UTC."
Step 3: Iterate if needed
If validation fails or fields are missing, adjust the Grok pattern and re-validate.
Step 4: Apply configuration
For External Adapters:
# Write config to temp file
cat > /tmp/adapter-config.yaml << 'EOF'
... existing config with updated parsing_rules ...
EOF
limacharlie external-adapter set --key <ADAPTER_NAME> --input-file /tmp/adapter-config.yaml --oid <SELECTED_ORG_ID>
For Cloud Sensors:
# Write config to temp file
cat > /tmp/cloud-adapter-config.yaml << 'EOF'
... existing config with updated parsing ...
EOF
limacharlie cloud-adapter set --key <SENSOR_NAME> --input-file /tmp/cloud-adapter-config.yaml --oid <SELECTED_ORG_ID>
Step 5: Verify no adapter errors
After applying the configuration for External Adapters or Cloud Sensors, check for any errors:
limacharlie org errors --oid <SELECTED_ORG_ID> --output yaml
Look for errors related to the adapter. If errors appear, review the adapter configuration and address the issues.
For One-off/USP Adapters:
Provide the configuration for the user to apply locally:
YAML Config:
file:
client_options:
platform: text
identity:
installation_key: <IID>
oid: <OID>
sensor_seed_key: my-adapter
mapping:
parsing_grok:
message: '%{SYSLOGTIMESTAMP:date} %{HOSTNAME:host} %{WORD:service}\[%{INT:pid}\]: %{GREEDYDATA:message}'
event_type_path: "service"
event_time_path: "date"
event_time_timezone: "America/New_York" # Required for SYSLOGTIMESTAMP (no timezone in pattern)
sensor_hostname_path: "host"
file_path: "/var/log/messages"
CLI Config:
./lc_adapter file \
client_options.identity.installation_key=<IID> \
client_options.identity.oid=<OID> \
client_options.platform=text \
client_options.sensor_seed_key=my-adapter \
"client_options.mapping.parsing_grok.message=%{SYSLOGTIMESTAMP:date} %{HOSTNAME:host} %{WORD:service}\[%{INT:pid}\]: %{GREEDYDATA:message}" \
client_options.mapping.event_type_path=service \
client_options.mapping.event_time_path=date \
client_options.mapping.event_time_timezone=America/New_York \
client_options.mapping.sensor_hostname_path=host \
file_path=/var/log/messages
IMPORTANT: The
event_time_timezoneis REQUIRED when usingSYSLOGTIMESTAMP,DATESTAMP, orTIMESTAMP_ISO8601without explicit timezone info. Without it, LimaCharlie assumes UTC and timestamps will be incorrect.
When outputting CLI config for test-limacharlie-adapter skill, use this format:
--grok '<PATTERN>'
--event-type <PATH>
--event-time <PATH>
--event-time-tz <TIMEZONE> # Include when timestamp pattern lacks timezone
--hostname-path <PATH>
User: "Help me parse syslog logs from my Linux server"
Sample log:
Jan 15 12:30:45 server01 sshd[12345]: Accepted publickey for admin from 192.168.1.50 port 54321 ssh2
Generated Grok pattern:
%{SYSLOGTIMESTAMP:date} %{HOSTNAME:host} %{WORD:service}\[%{INT:pid}\]: %{GREEDYDATA:message}
Mapping configuration:
mapping:
parsing_grok:
message: '%{SYSLOGTIMESTAMP:date} %{HOSTNAME:host} %{WORD:service}\[%{INT:pid}\]: %{GREEDYDATA:message}'
event_type_path: "service"
event_time_path: "date"
event_time_timezone: "America/New_York" # Required - SYSLOGTIMESTAMP has no timezone
sensor_hostname_path: "host"
CLI options for test-limacharlie-adapter:
--grok '%{SYSLOGTIMESTAMP:date} %{HOSTNAME:host} %{WORD:service}\[%{INT:pid}\]: %{GREEDYDATA:message}'
--event-type service
--event-time date
--event-time-tz America/New_York
--hostname-path host
Validation result:
{
"valid": true,
"results": [{
"event": {
"timestamp": "Jan 15 12:30:45",
"event_type": "sshd",
"message": "Accepted publickey for admin from 192.168.1.50 port 54321 ssh2"
},
"routing": {
"hostname": "server01"
}
}]
}
User: "I need to parse these firewall logs and extract the source/destination IPs"
Sample log:
2024-01-15T12:00:00Z ACCEPT TCP 192.168.1.100:54321 10.0.0.5:443 packets=1 bytes=78
Generated Grok pattern:
%{TIMESTAMP_ISO8601:timestamp} %{WORD:action} %{WORD:protocol} %{IP:src_ip}:%{NUMBER:src_port} %{IP:dst_ip}:%{NUMBER:dst_port} packets=%{NUMBER:packets} bytes=%{NUMBER:bytes}
Mapping configuration:
mapping:
parsing_grok:
message: '%{TIMESTAMP_ISO8601:timestamp} %{WORD:action} %{WORD:protocol} %{IP:src_ip}:%{NUMBER:src_port} %{IP:dst_ip}:%{NUMBER:dst_port} packets=%{NUMBER:packets} bytes=%{NUMBER:bytes}'
event_type_path: "action"
event_time_path: "timestamp"
Validation result:
{
"valid": true,
"results": [{
"event": {
"timestamp": "2024-01-15T12:00:00Z",
"event_type": "ACCEPT",
"protocol": "TCP",
"src_ip": "192.168.1.100",
"src_port": "54321",
"dst_ip": "10.0.0.5",
"dst_port": "443",
"packets": "1",
"bytes": "78"
}
}]
}
User: "I have a local adapter running, just give me the config to parse Apache access logs"
Sample log:
192.168.1.50 - admin [15/Jan/2024:12:30:45 +0000] "GET /api/users HTTP/1.1" 200 1234
Generated configuration (YAML):
file:
client_options:
platform: text
identity:
installation_key: <YOUR_IID>
oid: <YOUR_OID>
sensor_seed_key: apache-logs
mapping:
parsing_grok:
message: '%{COMMONAPACHELOG}'
event_type_path: "verb"
event_time_path: "timestamp"
file_path: "/var/log/apache2/access.log"
Generated configuration (CLI):
./lc_adapter file \
client_options.identity.installation_key=<YOUR_IID> \
client_options.identity.oid=<YOUR_OID> \
client_options.platform=text \
client_options.sensor_seed_key=apache-logs \
"client_options.mapping.parsing_grok.message=%{COMMONAPACHELOG}" \
client_options.mapping.event_type_path=verb \
client_options.mapping.event_time_path=timestamp \
file_path=/var/log/apache2/access.log
LimaCharlie supports these pre-built patterns for common log formats:
| Pattern | Use Case |
|---|---|
%{COMMONAPACHELOG} | Apache common log format |
%{COMBINEDAPACHELOG} | Apache combined log format |
%{NGINXACCESS} | Nginx access logs |
For logs with key=value format (like CEF), use parsing_re with key-value extraction:
mapping:
parsing_re: '(?:<\d+>\s*)?(\w+)=(".*?"|\S+)'
This regex captures pairs where:
Use / as separator for nested JSON navigation:
a → access field aa/b/c → access nested field a.b.cThe event_type_path supports template strings for dynamic event type assignment based on event content.
LimaCharlie assumes UTC for timestamps that don't include explicit timezone information.
Formats with explicit timezone (safe):
Z: 2024-01-15T12:30:45Z2024-01-15T12:30:45-08:002024-01-15T12:30:45.000-08:00Formats without timezone (assumed UTC):
Jan 15 12:30:452024-01-15 12:30:45Recommendation: Configure log sources to emit UTC timestamps when possible. For syslog on Linux:
$ActionFileDefaultTemplate RSYSLOG_FileFormat in rsyslog.conf\[ for literal brackets)%{DATA} for non-greedy matching, %{GREEDYDATA} only at end of patternUsing stale timestamps: Always calculate epoch timestamps dynamically with Bash before querying get_historic_events. Never use placeholder or hardcoded values - the API will return empty results for timestamps outside the data retention window.
Wrong mapping structure: The mapping object should have parsing_grok, event_type_path, and event_time_path at the same level - NOT nested under a parsing key.
Wrong Grok field name: For text platform adapters, always use message as the key in parsing_grok, not text.
DATESTAMP vs TIMESTAMP_ISO8601:
YYYY-MM-DD HH:MM:SS → Use %{TIMESTAMP_ISO8601}MM-DD-YY HH:MM:SS → Use %{DATESTAMP}Jan 15 12:30:45 → Use %{SYSLOGTIMESTAMP}Not recognizing unparsed events: If you see event_type: "unknown_event" with only a text field, that means parsing is not configured - the text field contains the raw log data that needs a Grok pattern.
Timezone assumptions for timestamps without timezone info:
SYSLOGTIMESTAMP, DATESTAMP, and TIMESTAMP_ISO8601 without Z or offset don't include timezoneFor searchable fields, configure custom indexing:
indexing:
- events_included: ["*"]
path: "src_ip"
index_type: "ip"
- events_included: ["*"]
path: "dst_ip"
index_type: "ip"
Supported index types: file_hash, file_path, file_name, domain, ip, user, service_name, package_name
detection-engineering: Create D&R rules to detect patterns in parsed logstest-limacharlie-adapter: Deploy a test adapter to verify parsinglookup-lc-doc: Search LimaCharlie documentation for more parsing detailsFor more details on parsing configuration:
./docs/limacharlie/doc/Sensors/Adapters/adapter-usage.md./docs/limacharlie/doc/Sensors/Reference/logcollectionguide.md