CYART // SCHEMA · LOG-NORMALIZER7 NODES · 8 LINKSACTOR · Raw line — syslog, JSON, CEFACTOR01Raw linesyslog, JSON, CEFSERVICE · Format detector — sniff + source hintSERVICE02Format detectorsniff + source hintSERVICE · Parser: JSONSERVICE03Parser: JSONSERVICE · Parser: CEFSERVICE04Parser: CEFSERVICE · Parser: regex — grok-likeSERVICE05Parser: regexgrok-likeSERVICE · Field mapper — → OCSF / ECS schemaSERVICE06Field mapper→ OCSF / ECS schemaSTREAM · Event — typed, timestampedSTREAM07Eventtyped, timestamped

Reading logs for triage

Follow one suspicious login through identity, endpoint and cloud logs. Anatomy of a useful log line, why you read sequences instead of errors, time and normalisation traps, and the shell and SIEM queries that get you to the answer.

● Must know for interviewsFREELesson·11 min read·Beginner → Intermediate
logstriageinvestigationSIEM

01Anatomy of a useful log line

✓What you will learn
  • Read a log line for actor, action, target, outcome, time
  • Reconstruct a sequence across sources
  • Avoid the time and normalisation traps that wreck timelines
  • Pivot with shell one-liners and SIEM queries on huge data
A line worth reading twicejson
{"time":"2026-11-13T03:14:07Z","class":"authentication","actor":{"user":"priya@cyart.io","ip":"203.0.113.7","device_id":"unknown"},
 "target":{"app":"okta"},"outcome":"success","reason":"mfa_push_approved","session_id":"s_7fa1","geo":"RO"}

Every useful security log answers five things: who (actor identity and where from), did what (action), to what (target), with what result (outcome and reason), when (UTC, precise). This one says: at 03:14 UTC, Priya's account authenticated successfully from Romania on an unknown device, via an approved MFA push. Each of those facts alone is explainable. Together they are a hypothesis: MFA fatigue from a stolen password.

FieldPivot toQuestion
session_idEverything that session did in Okta and downstream appsWhat was accessed after the login?
ipAll logins from this IP across usersIs this one account or a campaign?
userLast 30 days of loginsHas this user ever been in RO or on an unknown device?
timeEDR and cloud audit ±15 minWhat happened on their laptop and in AWS around then?
◎Interview radar

Interviewers hand you one log line and ask "what does this tell you?". Strong candidates read the five fields aloud, say which are unusual, and name the next two pivots. Weak ones jump straight to a verdict.

02Read sequences, not errors

Beginners grep for ERROR and failure. Attackers mostly generate successes. Read the sequence: failed pushes, then an approved push at 3 a.m., then a new mail-forwarding rule, then a download of 400 files from SharePoint. No single line is an error.

▭On the whiteboard · Triage timeline3 points
01
02:58-03:13
6 MFA push denied from 203.0.113.7 (RO)
02
03:14
push approved, unknown device, session s_7fa1
03
03:19-03:41
mail rule 'forward to gmail' created; 412 files downloaded

A timeline is a table with four columns: UTC time, source, entity, event. Add rows from every source you touch, sort by time, and the story writes itself. Gaps are findings too: a host that goes silent for 20 minutes during an intrusion may have had its agent killed.

“How to say it in the room

"I don't look for errors, I build the timeline. Six denied pushes then an approval at 3 a.m. from a new country is a fatigue attack until proven otherwise, and the mail rule five minutes later proves it."

Test yourself

Which log line is the strongest single indicator of compromise here?

03Time, normalisation and the traps

Most broken timelines are broken by time, not by attackers. Sources log in different zones, different formats and with clocks that drift. Normalise before you reason.

TrapWhat it looks likeFix
Local time zonesFirewall in IST, IdP in UTC: the login appears 5.5 hours after the downloadConvert everything to UTC on ingest; keep the original field too
Clock skewProcess starts before the login that spawned itCheck NTP health; prefer the collector's receive time as a tiebreak
Ingest delaySearch for the last 15 minutes misses events still in the pipelineSearch on event time with a margin; know each source's lag
Field name driftsrc_ip, client.ip, IpAddress across three sourcesMap to one schema (ECS, OCSF) in the pipeline, not in your head
RetentionThe VPN log you need rotated out after 7 daysKnow retention per source before the incident, not during it
FigNormalise once, query everywhere
CYART // SCHEMA · LOG-NORMALIZER7 NODES · 8 LINKSACTOR · Raw line — syslog, JSON, CEFACTOR01Raw linesyslog, JSON, CEFSERVICE · Format detector — sniff + source hintSERVICE02Format detectorsniff + source hintSERVICE · Parser: JSONSERVICE03Parser: JSONSERVICE · Parser: CEFSERVICE04Parser: CEFSERVICE · Parser: regex — grok-likeSERVICE05Parser: regexgrok-likeSERVICE · Field mapper — → OCSF / ECS schemaSERVICE06Field mapper→ OCSF / ECS schemaSTREAM · Event — typed, timestampedSTREAM07Eventtyped, timestamped
  • ACTOR
  • SERVICE
  • STREAM
  • Live flow
  • Lookup / async
//Raw events arrive in many shapes. The normaliser parses, maps fields to a common schema, converts time to UTC and enriches with asset and identity context, so an analyst writes one query instead of six.
!Absence of evidence

No hits in the SIEM can mean nothing happened, or that the source was never onboarded, the parser broke, or the data aged out. Before you write "no activity observed", confirm the source had data for that host and window.

04Pivoting at scale: shell and SIEM

You will meet logs as compressed files on a box and as tables in a SIEM. The moves are the same: filter early, project the fields you need, count, sort, look at the rare values.

Shell that scalesbash
# One user's identity timeline, compact
zstdcat okta-2026-11-13.jsonl.zst | jq -r 'select(.actor.user=="priya@cyart.io") | "\(.time) \(.outcome) \(.reason // "") \(.actor.ip) \(.geo // "")"' | sort

# All users touched by the suspicious IP
zstdcat okta-*.jsonl.zst | jq -r 'select(.actor.ip=="203.0.113.7") | .actor.user' | sort | uniq -c | sort -rn | head

# Huge rotated files: never cat; stream and filter early
zgrep -h '"session_id":"s_7fa1"' app-*.log.gz | sort -k1 | less
Who else did this IP touch?kql
// Same pivot in a SIEM (Microsoft Sentinel, KQL)
SigninLogs
| where TimeGenerated between (datetime(2026-11-13 02:00) .. datetime(2026-11-13 05:00))
| where IPAddress == "203.0.113.7"
| summarize attempts=count(), ok=countif(ResultType == "0"), users=make_set(UserPrincipalName) by IPAddress, Location
| order by attempts desc

sort | uniq -c | sort -rn is the most useful pipeline in security. It turns a million lines into a frequency table, and the interesting values usually sit at the bottom (rare) or the top (noisy attacker).

“How to say it in the room

"My first three pivots on any suspicious login are the session, the IP across all users, and the user's own 30-day history. That tells me in five minutes whether it is one confused user, one compromised account, or a campaign."

Q1What is the first thing you do once you believe it?+

Contain: revoke the session and all refresh tokens, reset the password, remove the mail rule, then investigate the downloads. Evidence first: export the logs to the case before anything rotates.

Q2Logs are in six consoles. How do you cope?+

Normalise into one schema with a shared time base, which is why the SIEM pipeline exists. Until then, a spreadsheet timeline in UTC with a source column.

Q3How do you know a source is complete?+

Check volume over time for that source and host. A sudden drop to zero is a pipeline or agent problem, and possibly tampering, not a quiet night.

Test yourself

A SIEM search for a host's process events during the incident window returns nothing. What do you check first?

Finished this one?
Tick it and it shows as done on the roadmap and the course.