Diagnostic output written with Console or Debug.Print instead of a logger

ID

vbnet.maintainability.print_in_production

Severity

low

Remediation Complexity

medium

Remediation Risk

low

Remediation Effort

low

Resource

Api Design

Language

VB.NET

Description

Reports diagnostic output written straight to the process: Console.Write, Console.WriteLine and Debug.Print. These are the forms typically added while chasing a problem and then left behind. Calls to a logging abstraction, and Trace output - which is routed through configurable listeners rather than fixed to the console - are not reported. Test sources are excluded, and so is console output inside a Module that declares the program’s Sub Main entry point, because in a console application the console is the program’s own interface rather than a debugging channel. MsgBox is not reported either: in VB.NET it is the WinForms way of talking to the user, so in a desktop application it is the program’s interface and not a diagnostic channel, and the two cannot be told apart from the call site.

Rationale

Output written this way leaves the application with no way to control it. A logger decides per message whether it is emitted, at what level, with what correlation identifier, and to which destination; Console.WriteLine decides none of that at the point of use and nothing can turn it off short of editing the code. Where the process runs as a service, a container entry point or a web application, standard output is frequently discarded outright, so the message that was meant to explain a failure is simply gone - and the line still costs a formatted string and a synchronized write on every call, on a stream that in some hosts is slow and lock-contended. Because these calls carry no level and no structure, they also cannot be filtered or searched alongside real log output, so the one useful diagnostic in a batch job hides in a wall of console noise. Left-behind statements are a maintenance signal too - they mark code that was debugged by printing, and they sometimes print values that were never meant to be exposed.

The following code illustrates the pattern detected by this rule:

Public Sub Post(ByVal reference As String, ByVal amount As Decimal)
    ' FLAGGED: Diagnostic output written with Console or Debug.Print instead of a logger
    Console.WriteLine("posting " & reference)
    ' FLAGGED: Diagnostic output written with Console or Debug.Print instead of a logger
    Console.Write(amount)
    ' FLAGGED: Diagnostic output written with Console or Debug.Print instead of a logger
    Debug.Print("amount was " & amount.ToString())
    Try
        Send(reference, amount)
    Catch ex As InvalidOperationException
        ' MsgBox is the WinForms way of talking to the user, so it is the program's
        ' interface in a desktop application and cannot be told apart from a leftover
        ' debugging dialog at the call site. Not reported.

Remediation

Route the message through the application’s logging abstraction and choose a level that matches it: Debug or Trace for the detail that was being chased, Information for something worth keeping, Warning or Error for a genuine problem, passing the exception object rather than just its message. If the statement was only ever scaffolding, delete it. In a genuine console application, keep Console output for the program’s real output - the results the user asked for - and log diagnostics separately.

' Before: unconditional, unstructured, and often discarded by the host
Console.WriteLine("posting " & reference)
Debug.Print("amount was " & amount.ToString())

' After
_log.Debug("Posting \{Reference} for \{Amount}", reference, amount)
_log.Error(ex, "Could not post \{Reference}", reference)

Configuration

This detector does not need any configuration.