Metadata-Version: 2.4
Name: printer-logging
Version: 1.1.0
Summary: Libreria Python per logging/stampa formattata con prefissi coerenti, filtro visibilità, log strutturati JSON opzionali e gestione session/remote id.
Author: Marco
License-Expression: LicenseRef-Proprietary
Project-URL: Repository, https://gitlab.dimension.it/marco.moser/printer
Project-URL: Changelog, https://gitlab.dimension.it/marco.moser/printer/-/blob/main/CHANGELOG.md
Keywords: logging,structured-logging,json-logging,debugging,print,logger,session-id,log-codes
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Logging
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: azure
Requires-Dist: azure-storage-blob>=12.0.0; extra == "azure"
Requires-Dist: azure-identity>=1.0.0; extra == "azure"
Dynamic: license-file

# Printer Logging

Libreria Python per **logging/stampa formattata** con prefissi coerenti, filtro di visibilità, log strutturati JSON opzionali e gestione session/remote id.

## Caratteristiche

- **Prefissi coerenti**: custom logs, session id, log_code
- **Filtro di visibilità**: controllo tramite `DebuggingMode` (NORMAL, VERBOSE, DEBUG)
- **Output flessibile**: stdout/stderr o `logging.Logger` integrato
- **Log strutturati JSON**: opzionali quando `use_structured_logging=True`
- **Gestione log_code**: risoluzione automatica con fallback e mapping configurabile

## Installazione

```bash
pip install printer-logging
```

## Quickstart

### Uso diretto (classe Printer)

```python
from printer import Printer, DebuggingMode, set_session_id

set_session_id("abc-123")

p = Printer(
    debugging_mode=DebuggingMode.DEBUG,
    name="MyApp",
    use_structured_logging=False,
)

p.info("Hello")                      # default: print
p.warning("Bad request", log_code=400)
p.error("Boom", log_code=500)
```

### Uso con API funzionale (singleton)

```python
import printer
from printer import DebuggingMode

printer.configure_printer(
    debugging_mode=DebuggingMode.DEBUG,
    name="MyApp",
    use_structured_logging=True,
    default_output_type="log",
)

printer.info("Hello", log_code=200, category="REQUEST_RECEIVED")
printer.warning("Bad request", log_code=400)
printer.error("Error occurred", log_code=500)
```

## DebuggingMode

Controlla la visibilità dei log:

- `NORMAL`: mostra solo `WARNING`/`ERROR`/`CRITICAL`
- `VERBOSE`: mostra `INFO+` (esclude `DEBUG`)
- `PRODUCTION`: equivalente a `VERBOSE`
- `DEBUG`: mostra tutto

## Log strutturati JSON

Quando `use_structured_logging=True` e `output_type="log"`, viene emesso anche un JSON per ogni evento con:

- `timestamp`, `level`, `logger`, `message`, `log_code`
- `custom_logs_prefix`, `session_id`
- `context` (`state/phase/category`) se presenti
- `exception` quando applicabile

## Session ID

Gestito via `contextvars`, funziona anche in async:

```python
from printer import set_session_id, get_session_id

set_session_id("abc-123")
assert get_session_id() == "abc-123"
```

## Emoji e Code Name

Le emoji sono associate ai **log code** (non ai livelli). Vengono visualizzate
solo se configurate per il codice risolto e se `use_emoji=True` (default).

### Configurazione via costruttore

```python
from printer import Printer, DebuggingMode

p = Printer(
    debugging_mode=DebuggingMode.DEBUG,
    emoji_by_code={
        200: "🟢",
        201: "✅",
        400: "🟡",
        500: "🔴",
    },
    name_by_code={
        200: "OK",
        400: "BAD_REQUEST",
        500: "SERVER_ERROR",
    },
)

p.info("Operazione completata", log_code=200)
# output: 🟢  INFO: [CUSTOM_LOGS] - [200] - Operazione completata

p.error("Errore interno", log_code=500)
# output: 🔴 ERROR: [CUSTOM_LOGS] - [500] - Errore interno

p.info("Senza emoji", log_code=200, use_emoji=False)
# output: INFO: [CUSTOM_LOGS] - [200] - Senza emoji
```

### Configurazione via JSON (`log_codes_path`)

Nel file JSON puoi definire emoji e nome per ogni codice nel campo `codes`:

```json
{
  "default_by_level": { "INFO": 200, "WARNING": 400, "ERROR": 500 },
  "codes": {
    "200": { "name": "OK",           "emoji": "🟢" },
    "201": { "name": "CREATED",      "emoji": "✅" },
    "400": { "name": "BAD_REQUEST",  "emoji": "🟡" },
    "404": { "name": "NOT_FOUND",    "emoji": "🔍" },
    "500": { "name": "SERVER_ERROR", "emoji": "🔴" }
  }
}
```

```python
p = Printer(
    debugging_mode=DebuggingMode.DEBUG,
    log_code_resolver=my_resolver,  # oppure passa log_codes_path a configure_printer()
)
```

Con `configure_printer()` o `PrinterConfig`, usa il parametro `log_codes_path`:

```python
printer.configure_printer(
    debugging_mode=DebuggingMode.DEBUG,
    custom_logs_prefix=True,
    session_id_prefix=True,
    log_codes_path="log_codes.json",
)
```

Il `code_name` (se presente) appare anche nel JSON structured come campo `code_name`.

## Scrittura su file (log giornaliero)

Abilita la scrittura su file passando `log_dir` al costruttore o a `configure_printer()`.

- Un file `YYYY-MM-DD.log` per ogni giorno (rotazione automatica a mezzanotte UTC)
- Ogni riga ha un timestamp ISO-8601 UTC anteposto al messaggio già formattato
- La directory viene creata automaticamente se non esiste
- Thread-safe

```
2026-03-23T14:23:45.012Z | [CUSTOM_LOGS] - [SES-ID-abc123] - [200] - INFO: 🟢  Server avviato
2026-03-23T14:23:46.034Z | [CUSTOM_LOGS] - [SES-ID-abc123] - [500] - ERROR: 🔴 Connessione fallita
```

### Uso diretto (classe)

```python
from printer import Printer, DebuggingMode

# Cartella personalizzata
p = Printer(
    debugging_mode=DebuggingMode.DEBUG,
    log_dir="/var/log/myapp",
)

# Cartella di default (logs/ nella CWD)
p = Printer(
    debugging_mode=DebuggingMode.DEBUG,
    log_dir="",
)

p.info("Applicazione avviata")  # scrive anche su logs/2026-03-23.log
```

### Uso con singleton

```python
import printer
from printer import DebuggingMode

printer.configure_printer(
    debugging_mode=DebuggingMode.DEBUG,
    custom_logs_prefix=True,
    session_id_prefix=True,
    log_dir="logs",
)

printer.info("Server avviato")
```

### Uso con PrinterConfig

```python
from printer import PrinterConfig, DebuggingMode

config = PrinterConfig(
    debugging_mode=DebuggingMode.DEBUG,
    custom_logs_prefix=True,
    session_id_prefix=True,
    log_dir="/var/log/myapp",
)
printer.configure_printer_config(config)
```

## Azure Blob Storage

Abilita il logging su Azure Blob Storage passando un oggetto `AzureBlobLogConfig`.

Installa la dipendenza opzionale:

```bash
pip install printer-logging[azure]
```

Ogni giorno viene creato un **append blob** nella forma `{blob_prefix}/YYYY-MM-DD.log`.
L'append blob permette di accodare righe senza rileggere il blob esistente.

### Metodi di autenticazione

#### 1. Connection string (sviluppo locale / ambienti non-produzione)

```python
from printer import Printer, DebuggingMode, AzureBlobLogConfig

azure_config = AzureBlobLogConfig(
    container_name="my-logs",
    connection_string="DefaultEndpointsProtocol=https;AccountName=...;AccountKey=...;",
    blob_prefix="myapp/logs",
)

p = Printer(
    debugging_mode=DebuggingMode.DEBUG,
    azure_blob_config=azure_config,
)
```

#### 2. SAS Token

```python
azure_config = AzureBlobLogConfig(
    container_name="my-logs",
    connection_string="BlobEndpoint=https://account.blob.core.windows.net;SharedAccessSignature=sv=...",
    blob_prefix="myapp/logs",
)
```

#### 3. Managed Identity (consigliato in produzione su Azure)

```python
from azure.identity import DefaultAzureCredential
from printer import Printer, DebuggingMode, AzureBlobLogConfig

azure_config = AzureBlobLogConfig(
    container_name="my-logs",
    account_url="https://mystorageaccount.blob.core.windows.net",
    credential=DefaultAzureCredential(),
    blob_prefix="myapp/logs",
    create_container_if_not_exists=False,
)

p = Printer(
    debugging_mode=DebuggingMode.DEBUG,
    azure_blob_config=azure_config,
)
```

### Uso con singleton

```python
import printer
from printer import DebuggingMode, AzureBlobLogConfig
from azure.identity import DefaultAzureCredential

printer.configure_printer(
    debugging_mode=DebuggingMode.DEBUG,
    custom_logs_prefix=True,
    session_id_prefix=True,
    azure_blob_config=AzureBlobLogConfig(
        container_name="my-logs",
        account_url="https://mystorageaccount.blob.core.windows.net",
        credential=DefaultAzureCredential(),
        blob_prefix="myapp/logs",
    ),
)

printer.info("Applicazione avviata su Azure")
```

### Combinare file locale e Azure

```python
p = Printer(
    debugging_mode=DebuggingMode.DEBUG,
    log_dir="logs",                  # backup locale
    azure_blob_config=azure_config,  # sincronizzazione su Azure
)
```

### Permessi Azure richiesti

#### Ruoli RBAC

Per scrivere sui blob l'identità usata deve avere:

- **`Storage Blob Data Contributor`** sul container (o sullo storage account)

Per `create_container_if_not_exists=True`:

- **`Storage Blob Data Contributor`** a livello di **storage account**

#### SAS Token

Il SAS token deve avere i permessi: **Add** (a), **Create** (c), **Write** (w).

#### Abilitare Managed Identity su Azure Function App

1. Function App → **Identità** → **Assegnata dal sistema** → `Stato: Attivato`
2. Storage Account → **Controllo di accesso (IAM)** → Aggiungi ruolo
   `Storage Blob Data Contributor` alla Managed Identity della Function App

Per dettagli completi sui permessi e gli altri metodi di autenticazione (Service Principal,
variabili d'ambiente), consulta il [README completo](https://gitlab.dimension.it/marco.moser/printer).

## API Reference

### Metodi di Livello

Tutti i metodi di livello supportano i seguenti parametri comuni:

- `message` (str): Messaggio da loggare
- `output_type` (str, opzionale): `"print"` o `"log"`. Se `None`, usa `default_output_type`
- `log_code` (int, opzionale): Codice numerico (100-999) per il log
- `state` (str, opzionale): Stato corrente (influenza la risoluzione del `log_code`)
- `force` (bool): Se `True`, bypassa il filtro `DebuggingMode`
- `use_emoji` (bool): Se `True`, mostra emoji se configurata per il codice
- `**properties`: Metadati aggiuntivi (es. `category`, `phase`, `user_id`, ecc.)

#### `debug(message, ...)`

```python
p.debug("Debug message", log_code=200, state="initializing")
printer.debug("Debug info", category="startup", phase="boot")
```

#### `info(message, ...)`

```python
p.info("Application started", log_code=200)
printer.info("Request received", log_code=200, category="request", user_id=123)
```

#### `warning(message, ...)`

```python
p.warning("Deprecated API used", log_code=400)
printer.warning("Rate limit approaching", log_code=429, remaining=5)
```

#### `error(message, ...)`

```python
p.error("Failed to connect", log_code=500)
printer.error("Database error", log_code=503, db="primary", retry_count=3)
```

#### `critical(message, ...)`

```python
p.critical("System failure", log_code=500)
printer.critical("Out of memory", log_code=500, memory_usage="99%")
```

#### `success(message, ...)`

```python
p.success("Operation completed", log_code=200)
printer.success("User created", log_code=201, user_id=456)
```

### Metodi Utility

#### `header(message, char="=", length=80, force=False, **properties)`

Stampa un'intestazione formattata:

```python
p.header("Application Startup", char="=", length=50)
printer.header("Configuration", char="-", length=60)
```

#### `section(title, content, force=False)`

Stampa una sezione con titolo e contenuto:

```python
p.section("Database", "Connected to PostgreSQL 14.2")
printer.section("Settings", "Debug mode: ON\nLog level: INFO")
```

#### `custom(message, prefix="➡️", force=False)`

Stampa un messaggio custom con prefisso:

```python
p.custom("Custom log message", prefix="📝")
printer.custom("Processing started", prefix="⚙️")
```

#### `plain(message, force=False)`

Stampa un messaggio senza formattazione:

```python
p.plain("Raw output without formatting")
printer.plain("Simple text message")
```

### Metodi di Configurazione

#### `set_logger_name(name)`

Cambia il nome del logger:

```python
p.set_logger_name("MyNewLogger")
printer.set_logger_name("AppLogger")
```

#### `set_debugging_mode(mode)`

Cambia la modalità di debugging:

```python
from printer import DebuggingMode

p.set_debugging_mode(DebuggingMode.VERBOSE)
printer.set_debugging_mode(DebuggingMode.DEBUG)
```

### API Funzionale (Singleton)

Quando usi `import printer`, puoi configurare un singleton condiviso:

#### `configure_printer(...)`

Configura il singleton con parametri:

```python
import printer
from printer import DebuggingMode

printer.configure_printer(
    debugging_mode=DebuggingMode.DEBUG,
    custom_logs_prefix=True,
    session_id_prefix=True,
    name="MyApp",
    use_structured_logging=True,
    default_output_type="log",
    log_codes_path="log_codes.json",  # opzionale
    stacktrace_mode="exception_only",
)
```

#### `configure_printer_config(config)`

Configura usando un oggetto `PrinterConfig`:

```python
from printer import PrinterConfig, DebuggingMode

config = PrinterConfig(
    debugging_mode=DebuggingMode.DEBUG,
    name="MyApp",
    use_structured_logging=True,
)
printer.configure_printer_config(config)
```

#### `get_printer()`

Ottiene l'istanza singleton configurata:

```python
p = printer.get_printer()
p.info("Using singleton instance")
```

#### `set_printer(printer)`

Imposta manualmente il singleton:

```python
from printer import Printer, DebuggingMode

my_printer = Printer(debugging_mode=DebuggingMode.DEBUG)
printer.set_printer(my_printer)
```

#### `is_configured()`

Verifica se il singleton è configurato:

```python
if printer.is_configured():
    printer.info("Ready to log")
else:
    printer.configure_printer(...)
```

#### `reset_printer()`

Resetta il singleton (utile nei test):

```python
printer.reset_printer()
```

### Funzioni Utility

#### `set_session_id(session_id)`

Imposta il session ID nel contesto:

```python
from printer import set_session_id

set_session_id("abc-123")
```

#### `get_session_id()`

Ottiene il session ID corrente:

```python
from printer import get_session_id

session = get_session_id()  # "abc-123" o None
```

## Requisiti

- Python >= 3.9

## Documentazione completa

Per dettagli completi, esempi avanzati e configurazione, consulta il [README completo](https://gitlab.dimension.it/marco.moser/printer) nel repository.
