---
title: 'Event Logs'
source: 'https://academia.sh/en/courses/endpoint-security/event-logs'
course: 'Endpoint and System Hardening'
language: en
updated: '2026-09-26T03:02:51+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Measures endpoint log sources as coverage over foothold point types'
    - 'Separates records that name the item from records that only leave a trace: counts full, indirect, and blind visibility'
    - 'Calculates the configuration-item and daily-volume cost of opening closed sources'
---

# Event Logs

In a fictional endpoint fleet, twelve foothold point types are measured against seven log sources: under the default configuration, 932 of 3,078 (machine, type) pairs are visible by name and 1,092 are in no source at all; once every source is opened, the visible-by-name count rises to 2,356, 250 pairs stay blind, and the cost is 10.2 million extra records a day plus 5 configuration items per role profile.

The previous lesson counted persistence mechanisms type by type and recorded what each
protection layer misses. The only thing that can show a missed item was missed is a record: if
the protection layer could not stop it, all that is left is what it saw. This topic measures
those records, and the first question is the plainest one — when a foothold is set up on the
endpoint, which source writes it down.

This topic's unit of measure is **visibility**, and its two columns must not be conflated. A
record either writes the item **by name** (which scheduled task, which service definition, which
login script) or only shows that something happened: a session opened, a process started, a
configuration changed. The second kind starts an investigation, it does not answer it. The
measurement below is a **model** and the regional measurement network is **fictional**; source
type is used, not product name.

- **SL1.** The endpoint fleet is fictional: 84 field laptops, 46 branch endpoints, 120 meter
  collector machines, 18 central processing servers. Platform families are referred to by their
  security model, as in the Information Technology and Systems Fundamentals course: the family
  with discrete user and group permissions, the family with access control lists, the family
  relying on a signed app store.
- **SL2.** There are twelve foothold point types, and not every type exists in every family; the
  family with access control lists has no counterpart for three of them.
- **SL3.** A source sees a type **fully** (writes the item by name), **indirectly** (the event is
  recorded, the item stays unnamed), or not at all.
- **SL4.** Two sources are closed by default: the process creation log and the scheduler log.
  Turning them on requires a configuration item and brings volume with it. Log volumes are
  per-machine averages and are fictional.
- **SL5.** The application audit log exists only on machines that run the application; the
  management plane audit log only on centrally managed machines. Both were measured in earlier
  courses; here they are just sources.

## What the Sources See

```js
// source.mjs — coverage between log sources and foothold point types. MODEL.
// The regional measurement network is FICTIONAL; no real organization, machine, or product name is used.

// Foothold point type -> which platform families it exists in.
// a: family with discrete user and group permissions, e: family with access control lists,
// i: family relying on a signed app store.
export const POINT = {
  "startup entry": "aei", "system service definition": "aei", "user service definition": "ai",
  "scheduled task": "aei", "login script": "aei", "desktop autostart entry": "aei",
  "package install hook": "ai", "kernel module": "aei", "bootloader boot entry": "ai",
  "management agent plugin": "aei", "in-app scheduled job": "aei",
  "firmware startup entry": "aei",
};

// Source: open by default?, log records/machine (fictional), configuration items to turn it on,
// type it sees -> "full" (writes the item by name) or "indirect" (event visible, item unnamed).
export const SOURCE = {
  "system event log": { open: true, volume: 4200, item: 0, sees: {
    "system service definition": "full", "kernel module": "full",
    "startup entry": "indirect", "management agent plugin": "indirect" } },
  "authentication log": { open: true, volume: 1900, item: 0, sees: {
    "login script": "indirect", "desktop autostart entry": "indirect" } },
  "process creation log": { open: false, volume: 38000, item: 3, sees: {
    "startup entry": "full", "system service definition": "full",
    "user service definition": "full", "scheduled task": "full", "login script": "full",
    "desktop autostart entry": "full", "package install hook": "full",
    "in-app scheduled job": "full", "bootloader boot entry": "indirect",
    "kernel module": "indirect", "management agent plugin": "indirect" } },
  "package and installation log": { open: true, volume: 40, item: 0, sees: {
    "package install hook": "full", "kernel module": "indirect" } },
  "scheduler log": { open: false, volume: 320, item: 2, sees: {
    "scheduled task": "full" } },
  "application audit log": { open: true, volume: 2400, item: 0, sees: {
    "in-app scheduled job": "full" } },
  "management plane audit log": { open: true, volume: 60, item: 0, sees: {
    "management agent plugin": "full", "firmware startup entry": "full" } },
};

// Machine group: family, machine count, sources present on it (fictional inventory).
export const GROUP = [
  ["field laptop", "i", 84, ["system event log", "authentication log",
    "process creation log", "package and installation log", "scheduler log"]],
  ["branch endpoint", "e", 46, ["system event log", "authentication log",
    "process creation log", "package and installation log", "scheduler log"]],
  ["meter collector", "a", 120, ["system event log", "authentication log",
    "process creation log", "package and installation log", "scheduler log",
    "application audit log"]],
  ["central processing", "a", 18, Object.keys(SOURCE)],
];

// For one type in one group: the best among open sources is "full", "indirect", or "none".
export const visibility = (type, sources, allOpen) => {
  let best = "none";
  for (const s of sources) {
    if (!allOpen && !SOURCE[s].open) continue;
    const g = SOURCE[s].sees[type];
    if (g === "full") return "full";
    if (g === "indirect") best = "indirect";
  }
  return best;
};
export const types = (family) => Object.keys(POINT).filter((t) => POINT[t].includes(family));

const row = (B) => (h) => h.map((v, j) => (j ? String(v).padStart(B[j])
  : String(v).padEnd(B[0]))).join("");
const line = row([33, 12, 8, 11, 9, 16]);

if (import.meta.url === `file://${process.argv[1]}`) {
  console.log(`model: ${Object.keys(POINT).length} foothold point types, ` +
    `${Object.keys(SOURCE).length} log sources, ${GROUP.length} machine groups (fictional)`);
  console.log(line(["log source", "default", "full", "indirect", "blind",
    "rec./day/mach."]));
  for (const [name, k] of Object.entries(SOURCE)) {
    const d = Object.values(k.sees);
    console.log(line([name, k.open ? "open" : "closed", d.filter((x) => x === "full").length,
      d.filter((x) => x === "indirect").length,
      Object.keys(POINT).length - d.length, k.volume]));
  }
  const byDefault = Object.keys(POINT).filter((t) =>
    visibility(t, Object.keys(SOURCE), false) === "none");
  const withAll = Object.keys(POINT).filter((t) =>
    visibility(t, Object.keys(SOURCE), true) !== "full");
  console.log(`\ntypes no source writes by name even with all sources open: ` +
    `${withAll.length} (${withAll.join(", ")})`);
  console.log(`types left with no trace under the default open sources: ` +
    `${byDefault.length} (${byDefault.join(", ")})`);
}
```

```
model: 12 foothold point types, 7 log sources, 4 machine groups (fictional)
log source                            default    full   indirect    blind  rec./day/mach.
system event log                         open       2          2        8            4200
authentication log                       open       0          2       10            1900
process creation log                   closed       8          3        1           38000
package and installation log             open       1          1       10              40
scheduler log                          closed       1          0       11             320
application audit log                    open       1          0       11            2400
management plane audit log               open       2          0       10              60

types no source writes by name even with all sources open: 1 (bootloader boot entry)
types left with no trace under the default open sources: 3 (user service definition, scheduled task, bootloader boot entry)
```

The first thing that stands out in the table is that none of the three sources open by default on
every machine is broad. The system event log writes two of the twelve types by name, the
authentication log writes **none** of them — that source's job is not tracking items, it is
tracking sessions, and it sees only two types indirectly. The package and installation log writes
one type. The default configuration's coverage is these three rows.

The only source that writes eight types by name is the one that is **closed**. The process
creation log is broad because what foothold points have in common is that they eventually **run**
something; the process that installs the item and the job the item runs both create a process.
The same row carries the cost too: 38,000 records per machine per day, more than four times the
total of the other six sources combined.

The last two rows rescue three types. The in-app scheduled job appears only in the application's
own audit log; the management agent plugin and the firmware startup entry appear only in the
management plane audit log. These are types that cannot be seen **from inside** the endpoint:
firmware runs before the operating system, and the agent plugin is pushed from a central point
and leaves no installation record on the machine.

The last row shows the measurement's limit. Even with every source open, one type is never
written by name in any source: the bootloader boot entry is visible only indirectly — the process
creation log has processes that were started under it, but the entry itself is never
named anywhere.

## Coverage by Machine Group

```js
// coverage.mjs — coverage per machine group: default configuration vs. everything open.
import { POINT, SOURCE, GROUP, visibility, types } from "./source.mjs";

const count = (group, allOpen) => {
  const [, family, , sources] = group;
  const d = { full: 0, indirect: 0, none: 0, blindSpots: [] };
  for (const t of types(family)) {
    const g = visibility(t, sources, allOpen);
    d[g === "none" ? "none" : g]++;
    if (g === "none") d.blindSpots.push(t);
  }
  return d;
};
const extra = (group) => group[3].filter((s) => !SOURCE[s].open)
  .reduce((a, s) => ({ item: a.item + SOURCE[s].item, volume: a.volume + SOURCE[s].volume }),
    { item: 0, volume: 0 });

const row = (B) => (h) => h.map((v, j) => (j ? String(v).padStart(B[j])
  : String(v).padEnd(B[0]))).join("");
const line = row([20, 7, 10, 7, 7, 10, 7, 7, 11, 8, 13]);

console.log(line(["machine group", "family", "machines", "types", "full", "indirect", "none",
  "full+", "indirect+", "none+", "extra items"]));
let pairs = 0, fullDefault = 0, fullAll = 0, noneDefault = 0, noneAll = 0, dailyExtra = 0;
for (const g of GROUP) {
  const v = count(g, false), h = count(g, true), e = extra(g), n = g[2];
  pairs += n * types(g[1]).length;
  fullDefault += n * v.full; fullAll += n * h.full; noneDefault += n * v.none;
  noneAll += n * h.none;
  dailyExtra += n * e.volume;
  console.log(line([g[0], g[1], n, types(g[1]).length, v.full, v.indirect, v.none,
    h.full, h.indirect, h.none, e.item]));
}
const machines = GROUP.reduce((a, g) => a + g[2], 0);
console.log(`\nfleet: ${machines} machines, ${pairs} (machine, type) pairs`);
console.log(`default configuration: ${fullDefault} pairs visible by name, ${noneDefault} ` +
  `pairs in no source`);
console.log(`all sources open: ${fullAll} pairs visible by name, ${noneAll} pairs in no source`);
for (const g of GROUP) {
  const h = count(g, true);
  console.log(`${g[0].padEnd(20)} blind spots: ${h.blindSpots.join(", ") || "-"}`);
}
const dailyRecords = GROUP.reduce((a, g) => a + g[2] * g[3]
  .filter((s) => SOURCE[s].open).reduce((sum, s) => sum + SOURCE[s].volume, 0), 0);
console.log(`\ndaily records: default ${dailyRecords.toLocaleString("en-US")}, ` +
  `all open ${(dailyRecords + dailyExtra).toLocaleString("en-US")} ` +
  `(${((dailyRecords + dailyExtra) / dailyRecords).toFixed(1)}x)`);
console.log(`gain: +${fullAll - fullDefault} pairs become visible by name; cost is ` +
  `${GROUP.reduce((a, g) => a + extra(g).item, 0)} configuration items ` +
  `(${extra(GROUP[0]).item} per role profile) and ` +
  `${dailyExtra.toLocaleString("en-US")} extra records per day`);
console.log(`even with all sources open, ${noneAll} pairs stay blind; all are the same type ` +
  `and correspond to one of ${Object.keys(POINT).length} types`);
```

```
machine group        family  machines  types   full  indirect   none  full+  indirect+   none+  extra items
field laptop              i        84     12      3         4      5      9          2       1            5
branch endpoint           e        46      9      2         4      3      7          1       1            5
meter collector           a       120     12      4         4      4      9          2       1            5
central processing        a        18     12      6         3      3     11          1       0            5

fleet: 268 machines, 3078 (machine, type) pairs
default configuration: 932 pairs visible by name, 1092 pairs in no source
all sources open: 2356 pairs visible by name, 250 pairs in no source
field laptop         blind spots: firmware startup entry
branch endpoint      blind spots: firmware startup entry
meter collector      blind spots: firmware startup entry
central processing   blind spots: -

daily records: default 1,977,800, all open 12,247,560 (6.2x)
gain: +1424 pairs become visible by name; cost is 20 configuration items (5 per role profile) and 10,269,760 extra records per day
even with all sources open, 250 pairs stay blind; all are the same type and correspond to one of 12 types
```

At fleet scale the question is: how many of the 3,078 (machine, type) pairs are visible by name.
Under the default configuration the answer is **932**, less than a third; 1,092 pairs are not
visible in any source at all. This second number is the lesson's main finding, because a coverage
gap does not produce an alert: a closed source is silent, and the inventory's "logs are being
collected" line is still true.

The group rows show where the difference comes from. Central processing servers see six of the
twelve types by name under the default configuration, field laptops see three. The difference is
the **presence** of two sources; the application audit log and the management plane audit log are
sources that have no counterpart on a laptop. The weakest visibility at the endpoint sits on the
organization's most scattered and most physically unprotected machines.

The branch endpoint row calls for reading a distinction. In that group the type count is nine,
not twelve, and the number of invisible pairs is lower for that reason — three types **do
not exist** in that family; they are not closed, they never existed. If a coverage table's
percentage calculation does not separate these two situations, the narrowest platform reads as
the best-protected platform. What should be counted is not a ratio, it is the number of blind
spots left.

The indirect visibility column carries a cost too, and that cost does not show up in the table.
When an investigation starts from an indirect record, it has to go to the machine and look for
the answer: the record says a session was opened, not which script ran in that session. A record
that names the item by name is answered with a query; an indirect record is answered with a site
visit.

Opening the two closed sources makes 1,424 pairs visible by name. The cost has two parts: 5
configuration items per role profile — 20 across the fleet, because the setting is written per
profile, not per machine — and 10,269,760 extra records a day. Total volume rises from 1.98
million to 12.25 million, a **6.2x** increase. This line also sets up the question for this
topic's later lessons: volume is not just a number on its own, it means retention time and search
cost.

The remaining 250 pairs cannot be closed. All of them are the same type — the firmware startup
entry — and on the 250 machines that carry no management plane audit log, no source sees it.
Making this type visible is not a logging setting, it is a management-architecture decision: the
endpoints need to be connected to a centrally managed plane. Making an unclosable point **at
least visible** is this course's rule, and here even that cannot be done; the only option left is
comparing that type from the outside with a periodic integrity measurement.

## Summary

- The three sources open by default on every machine write three of the twelve foothold point
  types by name; the only source that writes eight types by name is the process creation log,
  which is **closed** by default.
- A record falls into one of three states: **full** visibility, which writes the item by name;
  **indirect** visibility, which shows the event without naming the item; and a blind spot, where
  no record exists at all. The authentication log sees no type fully, and sees two indirectly.
- Across the fleet, 932 of 3,078 (machine, type) pairs are visible by name under the default
  configuration and 1,092 are in no source at all; once the two closed sources are opened, the
  visible-by-name count rises to 2,356 and the blind count falls to 250.
- The cost is 5 configuration items per role profile and 10.2 million extra records a day; total
  volume rises to 6.2x.
- All of the remaining 250 blind pairs are the firmware startup entry and cannot be seen from
  inside the endpoint; the bootloader boot entry, meanwhile, is never named in any source — it
  leaves only an indirect trace.

## Next Step

In this lesson's coverage table, the authentication log looks like the weakest source because it
does not write a single type by name. But that source is looking at a different question: not the
item being installed, but the account being taken over. The next lesson takes that log on its own
and runs a form of experiment on a real fictional record set: it parses failed-login patterns to
ask how many events a chosen threshold misses, how many times it raises an alert needlessly, and
why a threshold counted per account goes completely silent.
