diff --git a/cli/base/src/main/java/org/lflang/cli/CliBase.java b/cli/base/src/main/java/org/lflang/cli/CliBase.java
index 0b61fa767b..3e95afb334 100644
--- a/cli/base/src/main/java/org/lflang/cli/CliBase.java
+++ b/cli/base/src/main/java/org/lflang/cli/CliBase.java
@@ -263,19 +263,19 @@ public void validateResource(Resource resource) {
         try {
           path = FileUtil.toPath(uri);
         } catch (IllegalArgumentException e) {
-          reporter.printError("Unable to convert '" + uri + "' to path." + e);
+          reporter.printError("Unable to convert '" + uri + "' to path. " + e);
         }
       }
       issueCollector.accept(
           new LfIssue(
               issue.getMessage(),
               issue.getSeverity(),
+              path,
               issue.getLineNumber(),
               issue.getColumn(),
               issue.getLineNumberEnd(),
               issue.getColumnEnd(),
-              issue.getLength(),
-              path));
+              issue.getLength()));
     }
   }
 
diff --git a/cli/base/src/main/java/org/lflang/cli/StandaloneIssueAcceptor.java b/cli/base/src/main/java/org/lflang/cli/StandaloneIssueAcceptor.java
index d8fd14640a..399473f30a 100644
--- a/cli/base/src/main/java/org/lflang/cli/StandaloneIssueAcceptor.java
+++ b/cli/base/src/main/java/org/lflang/cli/StandaloneIssueAcceptor.java
@@ -13,13 +13,19 @@
 public class StandaloneIssueAcceptor implements ValidationMessageAcceptor {
 
   @Inject private IssueCollector collector;
+  @Inject private ReportingBackend backend;
 
   boolean getErrorsOccurred() {
     return collector.getErrorsOccurred();
   }
 
   void accept(LfIssue lfIssue) {
-    collector.accept(lfIssue);
+    if (lfIssue.getSeverity() == Severity.INFO) {
+      // print info statements instead of collecting them
+      backend.printIssue(lfIssue);
+    } else {
+      collector.accept(lfIssue);
+    }
   }
 
   void accept(
@@ -37,12 +43,12 @@ void accept(
         new LfIssue(
             message,
             severity,
+            getPath(diagnostic),
             diagnostic.getLine(),
             diagnostic.getColumn(),
             diagnostic.getLineEnd(),
             diagnostic.getColumnEnd(),
-            diagnostic.getLength(),
-            getPath(diagnostic));
+            diagnostic.getLength());
 
     accept(lfIssue);
   }
diff --git a/cli/base/src/main/kotlin/org/lflang/cli/ReportingUtil.kt b/cli/base/src/main/kotlin/org/lflang/cli/ReportingUtil.kt
index b8e94083cf..b7c4826bdf 100644
--- a/cli/base/src/main/kotlin/org/lflang/cli/ReportingUtil.kt
+++ b/cli/base/src/main/kotlin/org/lflang/cli/ReportingUtil.kt
@@ -128,12 +128,12 @@ class Io @JvmOverloads constructor(
 data class LfIssue(
     val message: String,
     val severity: Severity,
-    val line: Int?,
-    val column: Int?,
-    val endLine: Int?,
-    val endColumn: Int?,
-    val length: Int?,
-    val file: Path?
+    val file: Path? = null,
+    val line: Int? = null,
+    val column: Int? = null,
+    val endLine: Int? = null,
+    val endColumn: Int? = null,
+    val length: Int? = null,
 ) : Comparable<LfIssue> {
 
     constructor(
@@ -199,7 +199,7 @@ class IssueCollector {
  *
  * @author Clément Fournier
  */
-class ReportingBackend constructor(
+class ReportingBackend(
     /** Environment of the process, contains IO streams. */
     private val io: Io,
     /** Header for all messages. */
@@ -253,18 +253,18 @@ class ReportingBackend constructor(
 
     /** Print an error message to [Io.err]. */
     fun printError(message: String) {
-        io.err.println(header + colors.redAndBold("error: ") + message)
+        printIssue(LfIssue(message, Severity.ERROR))
         errorsOccurred = true
     }
 
     /** Print a warning message to [Io.err]. */
     fun printWarning(message: String) {
-        io.err.println(header + colors.yellowAndBold("warning: ") + message)
+        printIssue(LfIssue(message, Severity.WARNING))
     }
 
     /** Print an informational message to [Io.out]. */
     fun printInfo(message: String) {
-        io.out.println(header + colors.bold("info: ") + message)
+        printIssue(LfIssue(message, Severity.INFO))
     }
 
     /**
@@ -277,18 +277,17 @@ class ReportingBackend constructor(
 
         val header = severity.name.lowercase(Locale.ROOT)
 
-        var fullMessage: String = this.header + colors.severityColors(header, severity) + colors.bold(": " + issue.message) + System.lineSeparator()
+        var fullMessage: String = this.header + colors.severityColors(header, severity) + colors.bold(": " + issue.message)
         val snippet: String? = filePath?.let { formatIssue(issue, filePath) }
 
         if (snippet == null) {
             filePath?.let { io.wd.relativize(it) }?.let {
-                fullMessage += " --> " + it + ":" + issue.line + ":" + issue.column + " - "
+                fullMessage += "\n --> " + it + ":" + issue.line + ":" + issue.column + " - \n"
             }
         } else {
-            fullMessage += snippet
+            fullMessage += "\n" + snippet + "\n"
         }
         io.err.println(fullMessage)
-        io.err.println()
     }
 
     private fun formatIssue(issue: LfIssue, path: Path): String? {
diff --git a/cli/lfc/src/main/java/org/lflang/cli/Lfc.java b/cli/lfc/src/main/java/org/lflang/cli/Lfc.java
index 021fb1c9fa..dd2d6fe9d2 100644
--- a/cli/lfc/src/main/java/org/lflang/cli/Lfc.java
+++ b/cli/lfc/src/main/java/org/lflang/cli/Lfc.java
@@ -193,7 +193,7 @@ private void invokeGenerator(List<Path> files, Path root, Properties properties)
       // Print all other issues (not errors).
       issueCollector.getAllIssues().forEach(reporter::printIssue);
 
-      this.io.getOut().println("Code generation finished.");
+      messageReporter.nowhere().info("Code generation finished.");
     }
   }
 
diff --git a/cli/lff/src/test/java/org/lflang/cli/LffCliTest.java b/cli/lff/src/test/java/org/lflang/cli/LffCliTest.java
index 4941a81046..5f5f09d977 100644
--- a/cli/lff/src/test/java/org/lflang/cli/LffCliTest.java
+++ b/cli/lff/src/test/java/org/lflang/cli/LffCliTest.java
@@ -230,7 +230,7 @@ public void testFormatDirectoryVerbose(@TempDir Path tempDir) throws IOException
 
     result.checkOk();
 
-    result.checkStdOut(containsString("Formatted src" + File.separator + "File.lf"));
+    result.checkStdErr(containsString("Formatted src" + File.separator + "File.lf"));
     dirChecker(tempDir).checkContentsOf("src/File.lf", equalTo(FILE_AFTER_REFORMAT));
   }
 
diff --git a/core/src/main/java/org/lflang/generator/GeneratorBase.java b/core/src/main/java/org/lflang/generator/GeneratorBase.java
index 76af5c5c25..4109ec96c9 100644
--- a/core/src/main/java/org/lflang/generator/GeneratorBase.java
+++ b/core/src/main/java/org/lflang/generator/GeneratorBase.java
@@ -664,10 +664,10 @@ public void printInfo(LFGeneratorContext.Mode mode) {
     messageReporter
         .nowhere()
         .info("Generating code for: " + context.getFileConfig().resource.getURI().toString());
-    messageReporter.nowhere().info("******** mode: " + mode);
+    messageReporter.nowhere().info("Generation mode: " + mode);
     messageReporter
         .nowhere()
-        .info("******** generated sources: " + context.getFileConfig().getSrcGenPath());
+        .info("Generating sources into: " + context.getFileConfig().getSrcGenPath());
   }
 
   /** Get the buffer type used for network messages */