Docs

Log

Custom events

Record your own events in the Log by declaring them in logs.events, with flat typed fields. Vinaya checks each one, refuses a wrong one precisely, and writes the rest like any other event.

logs.events
vinaya log emit
No code
Typed fields

About custom events

Your team may want to record events Vinaya does not know about, such as a deploy. You declare them in vinaya.config.json under logs.events. You write no code and add no new top-level key.

A declared event is written as one custom · recorded line, with the same header, destination and redaction as every other event. An event that does not match its declaration writes no custom line; the refusal is recorded instead as an operation event.

How it works

  1. 1

    Declare the event

    Add it under logs.events, with a type for each field. Vinaya checks the declarations when it loads the config.

  2. 2

    Emit it

    Call vinaya log emit <event> from a script, a CI step or another tool, or emit it from Vinaya code. Every declared field must be given.

  3. 3

    Vinaya checks the values

    A matching event is written to your Log destination. A wrong one is refused, with the reason and the field names, and the refusal is logged.

Declare an event

vinaya.config.json
{
  "logs": {
    "events": {
      "acme.deploy": {
        "fields": { "env": ["prod", "staging"], "count": "number", "note": "text", "ok": "boolean" }
      }
    }
  }
}

A field is "text", "number", "boolean", or a list of allowed words (like env above).

Declaration rules

RuleLimit
Name<namespace>.<event>, matching ^[a-z][a-z0-9_]*\.[a-z][a-z0-9_.]*$
Name lengthAt most 64 characters
Reserved namespacevinaya. cannot be declared
Fields per eventAt most 20
Field name^[a-z][a-z0-9_]*$, at most 64 characters
Field type"text", "number", "boolean", or a list of words
Word list1 to 50 words, no duplicates
Word^[a-z0-9][a-z0-9_-]*$, at most 64 characters
ShapeOnly fields next to the name; no nested values and no lists as values

A broken declaration is refused with a message that names the entry, and the field when one is at fault: logs.events "<name>": ….

Emit an event

Pass the fields as one JSON object, with --json or on stdin.

vinaya log emit acme.deploy --json '{"env":"prod","count":3,"note":"weekly","ok":true}'

vinaya log emit resolves the destination as a live event would: an unattended caller is held to the default-branch rule, but the command does not force itself unattended. It waits for the event to be delivered before it exits.

Exit codeWhenPrints
0The event is declared and every field is validThe event name and the destination kind: folder, server or none
1Refused, for one of the five reasons belowThe reason and the field names, never a value
2No event name, unreadable JSON, or JSON that is not a flat objectA usage message; nothing is recorded

Plug in a process that is not Vinaya

  1. 1

    Declare the event

    Add it under logs.events.

  2. 2

    Say what the work is

    Set VINAYA_WORK_REF (the unit of work, any opaque value) and VINAYA_FLOW (your way of working). You do not need VINAYA_ROLE or VINAYA_TASK.

  3. 3

    Emit it

    printf '{"env":"prod","service":"checkout-api"}' | vinaya log emit acme.deploy_started

Refusals

Every declared field is required. Vinaya checks in this order. The first reason that applies wins, and it names every field with that problem, sorted.

ReasonMeaning
undeclaredThe name is not in logs.events
missing_fieldA declared field was not given
extra_fieldA field was given that is not declared
wrong_typeA value is not its declared type, including nested values and lists
too_longA text value is over 500 characters

Configuration

Key or variableEffect
logs.eventsYour declarations
VINAYA_WORK_REF, VINAYA_FLOW, VINAYA_FLOW_VERSION, VINAYA_RUNTIME, VINAYA_SOURCEFill in the header for an emitter that is not Vinaya

What gets logged

A valid event writes one custom · recorded line:

FieldValue
nameThe declared name
fieldsA flat record of at most 20 entries: text up to 500 characters, a finite number, or a boolean

kind and event are fixed, and a custom event never sets its own header. It is valid only under a schema 3 header. Like every line, its text, fields included, is redacted before it is written; see Redaction and trust.

A refusal writes exactly one operation · completed line:

FieldValue
operationlog.emit
resultrefused
error_classThe reason
field_namesThe fields with that problem, never their values
targetThe event name if it is shaped like a name, else null

An extra field whose name is not shaped like a field name is recorded as <unrecordable>.

What's next