AnnouncementsIntroducing Arkoren

    Menu

    Events and Telemetry

    Observe successful object changes, interpret facade spans and metrics, and preserve storage privacy boundaries.

    What's on this page
    1. Connect a shared event bus and telemetry handle with a stable store name.
    2. React to successful writes and deletions without assuming durable event delivery.
    3. Interpret operation timing, page observations, metrics, and error classifications.
    4. Test facade observations and keep private object data out of diagnostics.
    On this page

    Deleting an image can require thumbnail cleanup, while a slow upload requires timing evidence. Storage exposes these as different observations: events announce successful facade writes and deletions; telemetry measures facade operations. Neither is a durable delivery receipt or a record of every native backend change.

    Examples use storage from arkoren.dev/core/service/storage, your configured driver, and an existing application app.

    Connecting Observers

    Pass the application’s bus and telemetry when constructing the facade:

    files := storage.New(driver, func(store *storage.Builder) {
    	store.Events(app.Events())
    	store.Telemetry(app.Telemetry())
    	store.Name("uploads")
    })

    The fixed store name labels events and telemetry; omitted or empty names report default. A name does not select a bucket or provide tenant isolation. Use a small stable set of names, not user identifiers.

    Events require an explicit bus. Without explicit telemetry, each operation uses the shared handle, initially a no-op. Observe through the facade: direct driver or native-client calls bypass its instrumentation and notifications.

    Object Events

    When your application needs to react to storage changes, subscribe to the typed topics on the same bus passed to Builder.Events:

    Topic Payload Successful Observation
    storage.ObjectWritten storage.ObjectWrittenEvent Put returns metadata after storing an object.
    storage.ObjectDeleted storage.ObjectDeletedEvent Delete succeeds, including for an absent object.

    The topic names are storage.object.written and storage.object.deleted. Written payloads contain Store and Object metadata: key, byte size, and modification time. Deleted payloads contain Store and Key. They contain no object bytes or signed URLs, but keys can still identify private documents.

    For thumbnail cleanup, import standard context. Here thumbnails is your existing service with Delete(context.Context, string) error, accepting the source image key:

    unsubscribe, err := app.Events().Listen(storage.ObjectDeleted,
    	func(ctx context.Context, payload storage.ObjectDeletedEvent) error {
    		if payload.Store != "uploads" {
    			return nil
    		}
    		return thumbnails.Delete(ctx, payload.Key)
    	})

    Check the registration error and retain unsubscribe until the listening component stops; call it during that component’s cleanup. See the event bus guide for listener ownership.

    Listeners run synchronously before the storage call returns, on its goroutine and with its context. Keep them short; hand slow work to a queue when needed. An error or panic is logged by the bus and stops subsequent listeners for that publication, but never changes the completed storage result. Notifications are not a substitute for durable work: arrange durable reconciliation when missing cleanup would leave important state inconsistent.

    PutBytes, PutFile, and PutFileAs reuse Put and publish the same write event. Failed calls publish nothing, even if replacement succeeded before a metadata lookup failed. Delete events do not prove the object existed. Concurrent writes can publish in a different order from backend replacement; do not reconstruct the current object from event order.

    Reads, listings, and signing publish no object event. A client using a signed PUT writes directly to the provider and also publishes none.

    Operation Timing

    Each facade call records one client span named storage {operation} and two metrics. Operations are open, put, stat, delete, list, and sign. Upload helpers reuse put; download helpers reuse open; Sign and SignURL reuse sign rather than adding helper spans.

    open ends when the object is acquired, not after its contents are consumed or closed. Later download read/write/close failures belong to the caller’s HTTP operation, not the completed storage span. put includes consuming the source and backend work, so a slow source can explain a long write duration. sign measures URL creation, not the client’s later transfer.

    Walk records one list per requested page. Construction records nothing, and there is no separate traversal instrument. Early stopping avoids observations for pages never requested; S3 read-ahead inside one driver page remains part of that single facade operation.

    The counter arkoren.storage.operations counts calls. The histogram arkoren.storage.operation.duration records seconds, with buckets from 0.001 to 60 seconds. Both label operation, store, and failure class when present:

    Attribute Meaning
    arkoren.storage.operation The facade operation.
    arkoren.storage.store Configured name or default.
    error.type Error class on failure.
    arkoren.storage.object.size Successful write size in bytes; span only.

    See Instrumented Components for the shared instrument catalog and Telemetry for exporter setup.

    Failure Classification

    Open or Stat returning storage.ErrNotFound is an ordinary missing-object outcome: the span is not marked failed and no error.type is set. Other errors mark the span failed and report their class, not their text.

    Known classes include storage.ErrInvalidKey, storage.ErrUnsupported, storage.ErrInvalidTTL, and storage.ErrInvalidSignOptions. Cancellation uses the context error class; other errors use their Go type. A permission failure is not silently reclassified as absence.

    If development uses memory storage and intentionally falls back from signing, expect sign failures classified as storage.ErrUnsupported. Keep this known capability difference separate from operational alerts; do not hide unexpected provider failures under the same fallback.

    Privacy Boundaries

    Automatic storage telemetry excludes keys, prefixes, cursors, signed URLs, payloads, and raw error text. Correlate through a suitably chosen application identifier and trace ID rather than adding a private key to every span. Event payloads include keys for listeners; review destinations before forwarding them.

    Facade exclusions do not sanitize native SDK middleware, credential providers, endpoints, or returned errors. If you add AWS otelaws instrumentation to a client passed through Native Access, SDK request spans can nest under facade spans, but follow their own data policies. Review native attributes and HTTP/body logging separately; never record signed URLs or credential-bearing endpoints in ordinary diagnostics.

    Testing Observations

    For a test with t *testing.T, import telemetrytest from arkoren.dev/core/service/telemetry/telemetrytest, telemetry from arkoren.dev/core/service/telemetry, and memorystorage from arkoren.dev/core/service/storage/memorystorage. Construct an isolated store:

    fake := telemetrytest.New()
    files := storage.New(memorystorage.New(), func(store *storage.Builder) {
    	store.Telemetry(fake.Telemetry(t))
    	store.Name("uploads")
    })

    Write a small object and assert the observation rather than exporting it:

    if _, err := files.PutBytes(t.Context(), "reports/a.txt", []byte("ready")); err != nil {
    	t.Fatal(err)
    }
    fake.AssertSpan(t, "storage put",
    	telemetry.String("arkoren.storage.store", "uploads"),
    )
    fake.AssertCounter(t, "arkoren.storage.operations", 1,
    	telemetry.String("arkoren.storage.operation", "put"),
    )

    This checks facade reporting, not backend durability or client transfer success. For events, attach a real in-process bus, capture typed payloads, and assert your listener’s effect; include failure and absent-delete paths when relevant.

    For every setting, see the storage API, event API, and telemetry API.

    Open a bulk documentation archive?

    This file contains every guide in its scope, not API declarations. Loading it all into an agent can exhaust its context window and increase processing time and cost. For coding tasks, start with the index and read only the relevant pages.

    Use the full archive for offline indexing, bulk processing, or deliberate large-context use.

    Browse the index instead
    Open archive