Imagine a logging system that starts with plain text and lets an application add formatting one layer at a time. A timestamp can sit outside a level prefix, JSON can wrap everything produced so far, and uppercase can affect either just the message or the JSON field name depending on where it appears in the chain.
Your task is deliberately focused: implement only FormatterDecorator, TimestampDecorator, LevelDecorator, JsonDecorator, and UpperCaseDecorator. The LogFormatter contract, PlainFormatter, and the LogPipeline test harness are already implemented in the starter code.
The shared decorator controls how a message travels through the chain:
FormatterDecorator(inner) stores one wrapped formatter.format(message) first asks inner to format the message, then applies the current layer's transformation.label() starts with the inner label and appends " -> " plus the current layer's tag.The concrete layers behave as follows:
TimestampDecorator prepends [2024-01-15 10:30:00] and uses the tag "timestamp".LevelDecorator(inner, level) stores its level, prepends [LEVEL] , and uses a tag such as "level(INFO)".JsonDecorator returns {"message": "<text>"} and uses the tag "json".UpperCaseDecorator converts the entire inner result to uppercase and uses the tag "upper".The provided LogPipeline adds and resets layers, enforces the six-layer limit, counts format calls, and exposes the current label through layers(). It accepts "timestamp", "json", and "upper" through wrap(kind), while wrapLevel(level) accepts "INFO", "WARN", "ERROR", or "DEBUG".
Order is observable. Level → timestamp → JSON produces {"message": "[2024-01-15 10:30:00] [INFO] Application started"}, while JSON → level → timestamp leaves the prefixes outside the JSON object. The examples call LogPipeline because it is the public harness, but your code belongs only in the five marked decorator classes.
Input:
Output:
Explanation: A pipeline with no layers returns the message it was given. The chain is just the plain base, and nothing has been counted as a layer.
Input:
Output:
Explanation: The level layer runs first and prepends [INFO] . The timestamp layer runs on that result. The JSON layer wraps everything the two below it produced.
0 <= message.length <= 60message contains letters, digits and spaces only.6 layers are on one pipeline.100 calls in total are made across all methods.Only the decorator classes are unfinished. Define each complete class at its marked location. The shared contract, base component, and public test harness are already implemented; do not rewrite them.
Full marks when `FormatterDecorator` implements `LogFormatter`, stores the wrapped formatter, transforms `inner.format(message)`, and builds its label from `inner.label()` plus its own tag. Lose points heavily when transformations run before the inner chain or labels are kept separately.
Full marks when timestamp, level, JSON, and uppercase each implement exactly one transformation and the matching tag, with `LevelDecorator` retaining its constructor-supplied level. Lose points for fixed levels, malformed JSON text, or applying transformations in a central switch.
Full marks when every concrete decorator can wrap any `LogFormatter`, repeated layers work, and changing wrap order changes the output naturally without a layer inspecting what it wraps. Lose points for special-casing combinations, mutating the supplied harness, or printing to stdout.
Passing every test is not enough on its own. A submission is accepted only when the design also clears the bar.
| Call | Returns |
|---|---|
| new LogPipeline() | null |
| layers() | "plain" |
| format("Application started") | "Application started" |
| layerCount() | 0 |
A pipeline with no layers returns the message it was given. The chain is just the plain base, and nothing has been counted as a layer.
Run checks these cases. Submit also runs a larger hidden set.

