Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/api/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
| [`database`](./database.md) | 22 |
| [`echo`](./echo.md) | 1 |
| [`files`](./files.md) | 10 |
| [`logs`](./logs.md) | 1 |
| [`membership`](./membership.md) | 18 |
| [`payments`](./payments.md) | 4 |
| [`public`](./public.md) | 2 |
57 changes: 57 additions & 0 deletions docs/api/logs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# API · Logs

Access with `norbix.api.logs`.

| Method | Verb | Path | Scope |
| --- | --- | --- | --- |
| `write_logs` | `POST` | `/{version}/logs` | `project` |

Write your app's own log entries. Send 1–100 entries in one call. They are
queued: the call returns at once with `{"accepted": n}`. Read them back on the
Hub with `norbix.hub.logs.get_logs` / `get_log` (see [HUB · Logs](../hub/logs.md)).

Each entry:

| Field | Required | Rule |
| --- | --- | --- |
| `title` | yes | ≤ 200 characters |
| `level` | no | `"information"`, `"warning"` or `"error"` |
| `message` | no | ≤ 8000 characters |
| `eventCode` | no | a-z, 0-9, `_ . : -`, ≤ 64; stored as `app:<code>` |
| `correlationId` | no | letters, digits, `_ -`, ≤ 64 |
| `timestamp` | no | ISO-8601 UTC string, at most 7 days old and 5 minutes ahead |
| `meta` | no | string → string map, ≤ 20 keys |

`projectId` is optional; the client's project is used when it is left out.

```python
from datetime import datetime, timezone

result = norbix.api.logs.write_logs(entries=[
{
"level": "error",
"title": "Checkout failed",
"message": "card declined",
"eventCode": "checkout.failed",
"correlationId": request_id,
"timestamp": datetime.now(timezone.utc).isoformat(),
"meta": {"orderId": order_id},
},
])
print(result["accepted"])
```

`timestamp` must be a string: pass `datetime.isoformat()`, not the `datetime`
itself.

## Refusals

| HTTP | Error | When |
| --- | --- | --- |
| 400 | `ValidationError` | an entry breaks a rule above |
| 400 | `ValidationError`, `CM-ERRORS-LOGGING-002` | the Logs module is off for the project |
| 429 | `RateLimitError`, `CM-ERRORS-LOGGING-003` | the project's per-minute quota is used up |

The SDK does not retry `write_logs` (it is a POST). On a 429 the gateway sends
a `Retry-After` header with the seconds to wait; the SDK does not expose
response headers on errors yet, so wait about a minute before you send again.
2 changes: 1 addition & 1 deletion docs/hub/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
| [`email`](./email.md) | 1 |
| [`files`](./files.md) | 23 |
| [`internal`](./internal.md) | 1 |
| [`logs`](./logs.md) | 9 |
| [`logs`](./logs.md) | 15 |
| [`membership`](./membership.md) | 25 |
| [`notifications`](./notifications.md) | 117 |
| [`payments`](./payments.md) | 16 |
Expand Down
40 changes: 40 additions & 0 deletions docs/hub/logs.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,50 @@ Access with `norbix.hub.logs`.
| --- | --- | --- | --- |
| `disable_logging` | `PUT` | `/{version}/logs/disable` | `project` |
| `enable_logging` | `PUT` | `/{version}/logs/enable` | `project` |
| `get_logs` | `GET` | `/{version}/logs` | `project` |
| `get_log` | `GET` | `/{version}/logs/{Id}` | `project` |
| `get_logs_by_correlation_id` | `GET` | `/{version}/logs/audit` | `project` |
| `clean_logs` | `POST` | `/{version}/logs/clean` | `project` |
| `get_log_settings` | `GET` | `/{version}/logs/settings` | `project` |
| `save_log_settings` | `POST` | `/{version}/logs/settings` | `project` |
| `delete_logging_integration` | `DELETE` | `/{version}/logs/integrations/{Id}` | `project` |
| `disable_logging_integration` | `PUT` | `/{version}/logs/integrations/{Id}/disable` | `project` |
| `enable_logging_integration` | `PUT` | `/{version}/logs/integrations/{Id}/enable` | `project` |
| `get_logging_integration` | `GET` | `/{version}/logs/integrations/{id}` | `project` |
| `get_logging_integrations` | `GET` | `/{version}/logs/integrations` | `project` |
| `save_logging_integration` | `POST` | `/{version}/logs/integrations` | `project` |
| `test_logging_integration` | `POST` | `/{version}/logs/integrations/test` | `project` |

Your app writes its own entries on the Api with `norbix.api.logs.write_logs`
(see [API · Logs](../api/logs.md)).

## Reading logs

`get_logs` takes the optional filters `level`, `module`, `logCorrelationId`,
`eventCode`, `search`, `metaKey` / `metaValue`, `fromUtc` / `toUtc` and the
cursor paging fields `pageSize`, `startingAfter`, `endingBefore`. An entry your
app wrote with `eventCode="order.paid"` is stored as `app:order.paid`.

```python
page = norbix.hub.logs.get_logs(level="error", eventCode="app:checkout.failed", pageSize=50)
for entry in page["list"]["items"]:
print(entry["id"], entry["title"])

one = norbix.hub.logs.get_log(page["list"]["items"][0]["id"])["item"]

settings = norbix.hub.logs.get_log_settings()
norbix.hub.logs.save_log_settings(skipHttpBodyMeta=True)

norbix.hub.logs.clean_logs() # deletes every entry stored in Norbix Logging
```

## Logging integrations

An integration sends the project's logs on to Datadog, AWS CloudWatch or
MongoDB. **After you save an integration, call `test_logging_integration`.
Nothing is delivered to an integration until its last test passed.**

```python
saved = norbix.hub.logs.save_logging_integration(**datadog_or_cloudwatch_or_mongodb_request)
norbix.hub.logs.test_logging_integration(integrationId=saved["id"])
```
3 changes: 3 additions & 0 deletions src/norbix_python/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from .database import AsyncDatabaseModule, DatabaseModule
from .echo import AsyncEchoModule, EchoModule
from .files import AsyncFilesModule, FilesModule
from .logs import AsyncLogsModule, LogsModule
from .membership import AsyncMembershipModule, MembershipModule
from .payments import AsyncPaymentsModule, PaymentsModule
from .public import AsyncPublicModule, PublicModule
Expand All @@ -16,6 +17,7 @@ def __init__(self, transport: Transport) -> None:
self.database = DatabaseModule(transport)
self.echo = EchoModule(transport)
self.files = FilesModule(transport)
self.logs = LogsModule(transport)
self.membership = MembershipModule(transport)
self.payments = PaymentsModule(transport)
self.public = PublicModule(transport)
Expand All @@ -27,6 +29,7 @@ def __init__(self, transport: AsyncTransport) -> None:
self.database = AsyncDatabaseModule(transport)
self.echo = AsyncEchoModule(transport)
self.files = AsyncFilesModule(transport)
self.logs = AsyncLogsModule(transport)
self.membership = AsyncMembershipModule(transport)
self.payments = AsyncPaymentsModule(transport)
self.public = AsyncPublicModule(transport)
77 changes: 77 additions & 0 deletions src/norbix_python/api/logs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""Write your app's own log entries on the Api (project scope).

``write_logs`` sends 1–100 entries in one call. They are queued and the call
returns at once with ``{"accepted": n}``; read them back on the Hub with
``hub.logs.get_logs`` / ``get_log`` (an entry's ``eventCode`` is stored as
``app:<code>``).
"""

from __future__ import annotations

from typing import Any

from ..transport import AsyncTransport, Transport


class LogsModule:
def __init__(self, transport: Transport) -> None:
self._transport = transport

def write_logs(
self,
*,
entries: list[dict[str, Any]],
timeout: float | None = None,
bearer_token: str | None = None,
**request: Any,
) -> Any:
"""POST /{version}/logs

Write 1–100 log entries. Each entry: ``title`` (required, ≤200),
``level`` (``"information"`` / ``"warning"`` / ``"error"``),
``message`` (≤8000), ``eventCode`` (a-z 0-9 ``_ . : -``, ≤64),
``correlationId`` (letters, digits, ``_ -``, ≤64), ``timestamp`` (an
ISO-8601 UTC string, ≤7 days old, ≤5 minutes ahead) and ``meta`` (a
string map, ≤20 keys). Optional ``projectId``. Gives back
``{"accepted": n}``.

Raises ``ValidationError`` (400) for a broken entry or
``CM-ERRORS-LOGGING-002`` when the Logs module is off, and
``RateLimitError`` (429, ``CM-ERRORS-LOGGING-003``) when the project's
per-minute quota is used up. The call is not retried.
"""
return self._transport.send(
target="api",
path="/{version}/logs",
method="POST",
path_params={},
request={"entries": entries, **request},
scope="project",
timeout=timeout,
bearer_token=bearer_token,
)


class AsyncLogsModule:
def __init__(self, transport: AsyncTransport) -> None:
self._transport = transport

async def write_logs(
self,
*,
entries: list[dict[str, Any]],
timeout: float | None = None,
bearer_token: str | None = None,
**request: Any,
) -> Any:
"""POST /{version}/logs — see :meth:`LogsModule.write_logs`."""
return await self._transport.send(
target="api",
path="/{version}/logs",
method="POST",
path_params={},
request={"entries": entries, **request},
scope="project",
timeout=timeout,
bearer_token=bearer_token,
)
2 changes: 2 additions & 0 deletions src/norbix_python/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,12 +264,14 @@ def __init__(
self.membership = api.membership
self.echo = api.echo
self.files = api.files
self.logs = api.logs
self.payments = api.payments
self.public = api.public
self.Database = self.database
self.Membership = self.membership
self.Echo = self.echo
self.Files = self.files
self.Logs = self.logs
self.Payments = self.payments
self.Public = self.public

Expand Down
Loading
Loading