Skip to content

Logging ​

botas logs operational events — incoming activities, token acquisition, handler errors — using each language's standard logging framework. You control the verbosity and destination by configuring the logger before your bot starts.


Quick Start ​

Enable debug logs to see what your bot is doing:

json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Botas": "Debug"         // ← all botas components at Debug level
    }
  }
}
bash
# Enable all botas logs
DEBUG=botas:* node index.js

# Or just errors and warnings
DEBUG=botas:error,botas:warn node index.js
python
import logging

logging.basicConfig(
    level=logging.DEBUG,      # ← show all levels including botas.* loggers
    format="%(levelname)s %(name)s: %(message)s"
)

.NET Logging ​

botas uses ASP.NET Core's ILogger<T> for all logging. The BotApp.Create(args) helper wires up dependency injection automatically so ILogger is injected into BotApplication.

Log levels ​

LevelWhen botas logs
TraceReceived activity type and payload details
InformationStarted bot listener, finished processing activity
ErrorError processing activity (includes exception)

Configure via appsettings.json ​

json
{
  "$schema": "https://json.schemastore.org/appsettings.json",
  "Logging": {
    "LogLevel": {
      "Default": "Warning",
      "Botas": "Information"      // ← all botas namespaces
    }
  }
}

Configure via launchSettings.json ​

json
{
  "$schema": "https://json.schemastore.org/launchsettings.json",
  "profiles": {
    "local": {
      "commandName": "Project",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development",
        "Logging__LogLevel__Botas": "Debug"   // ← override for development
      }
    }
  }
}

Configure with code ​

If you're not using BotApp, configure logging when building your WebApplication:

csharp
var builder = WebApplication.CreateBuilder(args);
builder.Logging.AddConsole();
builder.Logging.SetMinimumLevel(LogLevel.Debug);    // ← global minimum
builder.Services.AddBotApplication<BotApplication>();

For more details, see the ASP.NET Core logging docs.


Node.js Logging ​

botas defines a minimal Logger interface with five levels: trace, debug, info, warn, error. Three built-in loggers are available, or you can implement the Logger interface to integrate with pino, winston, etc.

Built-in loggers ​

LoggerDescription
debugLogger (default)Uses the debug package with botas:* namespaces. Enable via DEBUG=botas:*.
consoleLoggerWrites to console with a level prefix ([INFO], [ERROR], etc.).
noopLoggerDiscards all messages (useful in tests).

Log levels ​

Each level maps to a separate debug namespace so you can filter precisely:

bash
DEBUG=botas:*                         # all levels
DEBUG=botas:info,botas:warn,botas:error  # info and above
DEBUG=botas:error                     # errors only

Configure the logger ​

Call configure() once at application startup, before creating a BotApplication:

typescript
import { configure, debugLogger } from 'botas-core'

configure(debugLogger)      // ← optional: debugLogger is the default
typescript
import { configure, consoleLogger } from 'botas-core'

configure(consoleLogger)    // ← always logs to console (no DEBUG env var needed)
typescript
import { configure, noopLogger } from 'botas-core'

configure(noopLogger)       // ← silence all logs (useful in tests)
typescript
import { configure, consoleLogger, createOtelLogger } from 'botas-core'

// Use OTel logger when available, fall back to console
configure(createOtelLogger() ?? consoleLogger)

Custom logger (pino, winston, etc.) ​

Implement the Logger interface to integrate with any logging framework:

typescript
import { configure, type Logger } from 'botas-core'
import pino from 'pino'

const pinoInstance = pino({ level: 'debug' })

const pinoLogger: Logger = {
  trace: (msg, ...args) => pinoInstance.trace(msg, ...args),
  debug: (msg, ...args) => pinoInstance.debug(msg, ...args),
  info: (msg, ...args) => pinoInstance.info(msg, ...args),
  warn: (msg, ...args) => pinoInstance.warn(msg, ...args),
  error: (msg, ...args) => pinoInstance.error(msg, ...args),
}

configure(pinoLogger)

MSAL (token acquisition) logs ​

MSAL logs are automatically wired through to the botas logger via the token manager. You'll see messages like:

[INFO]  Acquired token for https://api.botframework.com/.default (expires in 3599s)

Python Logging ​

botas uses Python's stdlib logging module. Each module creates its own logger with logging.getLogger(__name__). Namespaces follow the module path: botas.bot_auth, botas.bot_http_client, etc.

Log levels ​

botas logs at the following levels:

LevelWhen botas logs
DEBUGToken acquisition, JWKS refresh, HTTP request details
INFOActivity received, handler dispatched, response sent
WARNINGRecoverable issues, unexpected responses
ERRORHandler exceptions, auth failures, network errors

Configure with basicConfig ​

python
import logging

logging.basicConfig(
    level=logging.DEBUG,                          # ← show all levels
    format="%(levelname)s %(name)s: %(message)s"  # ← include logger name
)

Configure specific loggers ​

python
import logging

# Show debug logs from botas only
logging.getLogger("botas").setLevel(logging.DEBUG)

# Or filter to specific modules
logging.getLogger("botas.bot_auth").setLevel(logging.DEBUG)
logging.getLogger("botas.bot_http_client").setLevel(logging.INFO)

Configure with dictConfig (production) ​

For more control (handlers, formatters, propagation), use logging.config.dictConfig:

python
import logging.config

logging.config.dictConfig({
    "version": 1,
    "formatters": {
        "standard": {
            "format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s"
        }
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "formatter": "standard",
        }
    },
    "loggers": {
        "botas": {
            "level": "INFO",
            "handlers": ["console"],
            "propagate": False,
        }
    }
})

For more details, see the Python logging docs.


What botas logs ​

Activity processing ​

Every incoming activity is logged at Info level (.NET / Node) or INFO (Python):

INFO: Received activity type=message from=user-id-123
INFO: Handler for 'message' completed in 42ms

Token acquisition (outbound auth) ​

When botas acquires an OAuth2 token for outbound API calls, you'll see:

DEBUG: Acquiring token for https://api.botframework.com/.default
INFO: Token acquired (expires in 3599s)

JWT validation (inbound auth) ​

When the Bot Service sends a JWT bearer token, botas validates it and logs:

DEBUG: Fetching JWKS from https://login.botframework.com/v1/.well-known/openid-configuration
DEBUG: Validating JWT signature with kid=abc123

Errors ​

Handler exceptions are logged at Error level (.NET / Node) or ERROR (Python):

ERROR: Error processing activity type=message: ArgumentNullException: Value cannot be null

When to use middleware for logging ​

If you need to log custom data (user IDs, conversation IDs, handler latency), use middleware instead of relying on botas's built-in logs:

csharp
using Botas;

public class CustomLoggingMiddleware : ITurnMiddleWare
{
    private readonly ILogger<CustomLoggingMiddleware> _logger;

    public CustomLoggingMiddleware(ILogger<CustomLoggingMiddleware> logger)
    {
        _logger = logger;
    }

    public async Task OnTurnAsync(
        TurnContext context,
        NextDelegate next,
        CancellationToken cancellationToken = default)
    {
        var start = DateTime.UtcNow;
        _logger.LogInformation("▶ {Type} from {User}",
            context.Activity.Type, context.Activity.From?.Id);

        await next(cancellationToken);

        var duration = (DateTime.UtcNow - start).TotalMilliseconds;
        _logger.LogInformation("◀ {Type} completed in {Duration}ms",
            context.Activity.Type, duration);
    }
}
typescript
import { getLogger } from 'botas-core'
import type { TurnMiddleware } from 'botas-express'

const customLoggingMiddleware: TurnMiddleware = async (context, next) => {
  const logger = getLogger()
  const start = Date.now()

  logger.info(`▶ ${context.activity.type} from ${context.activity.from?.id}`)
  await next()

  const duration = Date.now() - start
  logger.info(`◀ ${context.activity.type} completed in ${duration}ms`)
}
python
import logging
import time
from botas import TurnMiddleware
from botas.turn_context import TurnContext

_logger = logging.getLogger(__name__)

class CustomLoggingMiddleware(TurnMiddleware):
    async def on_turn(self, context: TurnContext, next) -> None:
        start = time.time()
        _logger.info(f"▶ {context.activity.type} from {context.activity.from_.id}")

        await next()

        duration = (time.time() - start) * 1000
        _logger.info(f"◀ {context.activity.type} completed in {duration:.0f}ms")

For more on middleware, see the Middleware guide.


Troubleshooting ​

Logs not appearing (.NET) ​

Check that appsettings.json exists and is set to Copy to Output Directory (in your .csproj):

xml
<ItemGroup>
  <Content Include="appsettings.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

Logs not appearing (Node.js) ​

If using debugLogger (the default), make sure the DEBUG environment variable is set:

bash
DEBUG=botas:* node index.js

Or switch to consoleLogger to always log to console:

typescript
import { configure, consoleLogger } from 'botas-core'
configure(consoleLogger)

Logs not appearing (Python) ​

Call logging.basicConfig() before creating your bot application:

python
import logging

logging.basicConfig(level=logging.DEBUG)   # ← call FIRST

from botas_fastapi import BotApp
app = BotApp()

Too many logs ​

Reduce the log level to show only warnings and errors:

json
{
  "Logging": {
    "LogLevel": {
      "Botas": "Warning"
    }
  }
}
bash
DEBUG=botas:warn,botas:error node index.js
python
logging.getLogger("botas").setLevel(logging.WARNING)

Learn More ​

BotAS — Multi-language Microsoft Teams bot library