diff --git a/src/StardewModdingAPI/Framework/LogFileManager.cs b/src/StardewModdingAPI/Framework/LogFileManager.cs
new file mode 100644
index 00000000..c2a2105b
--- /dev/null
+++ b/src/StardewModdingAPI/Framework/LogFileManager.cs
@@ -0,0 +1,46 @@
+using System;
+using System.IO;
+
+namespace StardewModdingAPI.Framework
+{
+ /// Manages reading and writing to log file.
+ internal class LogFileManager : IDisposable
+ {
+ /*********
+ ** Properties
+ *********/
+ /// The underlying stream writer.
+ private readonly StreamWriter Stream;
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// Construct an instance.
+ /// The log file to write.
+ public LogFileManager(string path)
+ {
+ // create log directory if needed
+ string logDir = Path.GetDirectoryName(path);
+ if (logDir == null)
+ throw new ArgumentException($"The log path '{path}' is not valid.");
+ Directory.CreateDirectory(logDir);
+
+ // open log file stream
+ this.Stream = new StreamWriter(path, append: false) { AutoFlush = true };
+ }
+
+ /// Write a message to the log.
+ /// The message to log.
+ public void WriteLine(string message)
+ {
+ this.Stream.WriteLine(message);
+ }
+
+ /// Release all resources.
+ public void Dispose()
+ {
+ this.Stream.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/StardewModdingAPI/Framework/Monitor.cs b/src/StardewModdingAPI/Framework/Monitor.cs
new file mode 100644
index 00000000..4fe1df04
--- /dev/null
+++ b/src/StardewModdingAPI/Framework/Monitor.cs
@@ -0,0 +1,104 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace StardewModdingAPI.Framework
+{
+ /// Encapsulates monitoring and logic for a given module.
+ internal class Monitor : IMonitor
+ {
+ /*********
+ ** Properties
+ *********/
+ /// The name of the module which logs messages using this instance.
+ private readonly string Source;
+
+ /// The log file to which to write messages.
+ private readonly LogFileManager LogFile;
+
+ /// The maximum length of the values.
+ private static readonly int MaxLevelLength = (from level in Enumerable.Cast(Enum.GetValues(typeof(LogLevel))) select level.ToString().Length).Max();
+
+ /// The console text color for each log level.
+ private static readonly Dictionary Colors = new Dictionary
+ {
+ [LogLevel.Trace] = ConsoleColor.DarkGray,
+ [LogLevel.Debug] = ConsoleColor.DarkGray,
+ [LogLevel.Info] = ConsoleColor.White,
+ [LogLevel.Warn] = ConsoleColor.Yellow,
+ [LogLevel.Error] = ConsoleColor.Red,
+ [LogLevel.Alert] = ConsoleColor.Magenta
+ };
+
+
+ /*********
+ ** Accessors
+ *********/
+ /// Whether to show trace messages in the console.
+ internal bool ShowTraceInConsole { get; set; }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// Construct an instance.
+ /// The name of the module which logs messages using this instance.
+ /// The log file to which to write messages.
+ public Monitor(string source, LogFileManager logFile)
+ {
+ // validate
+ if (string.IsNullOrWhiteSpace(source))
+ throw new ArgumentException("The log source cannot be empty.");
+ if (logFile == null)
+ throw new ArgumentNullException(nameof(logFile), "The log file manager cannot be null.");
+
+ // initialise
+ this.Source = source;
+ this.LogFile = logFile;
+ }
+
+ /// Log a message for the player or developer.
+ /// The message to log.
+ /// The log severity level.
+ public void Log(string message, LogLevel level = LogLevel.Debug)
+ {
+ this.LogImpl(this.Source, message, Monitor.Colors[level], level);
+ }
+
+ /// Log a message for the player or developer, using the specified console color.
+ /// The name of the mod logging the message.
+ /// The message to log.
+ /// The console color.
+ /// The log level.
+ [Obsolete("This method is provided for backwards compatibility and otherwise should not be used. Use " + nameof(Monitor) + "." + nameof(Monitor.Log) + " instead.")]
+ internal void LegacyLog(string source, string message, ConsoleColor color, LogLevel level = LogLevel.Debug)
+ {
+ this.LogImpl(source, message, color, level);
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// Write a message line to the log.
+ /// The name of the mod logging the message.
+ /// The message to log.
+ /// The console color.
+ /// The log level.
+ private void LogImpl(string source, string message, ConsoleColor color, LogLevel level)
+ {
+ // generate message
+ string levelStr = level.ToString().ToUpper().PadRight(Monitor.MaxLevelLength);
+ message = $"[{DateTime.Now:HH:mm:ss} {levelStr} {source}] {message}";
+
+ // log
+ if (this.ShowTraceInConsole || level != LogLevel.Trace)
+ {
+ Console.ForegroundColor = color;
+ Console.WriteLine(message);
+ Console.ResetColor();
+ }
+ this.LogFile.WriteLine(message);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/StardewModdingAPI/IMonitor.cs b/src/StardewModdingAPI/IMonitor.cs
new file mode 100644
index 00000000..8d20af01
--- /dev/null
+++ b/src/StardewModdingAPI/IMonitor.cs
@@ -0,0 +1,14 @@
+namespace StardewModdingAPI
+{
+ /// Encapsulates monitoring and logging for a given module.
+ public interface IMonitor
+ {
+ /*********
+ ** Methods
+ *********/
+ /// Log a message for the player or developer.
+ /// The message to log.
+ /// The log severity level.
+ void Log(string message, LogLevel level = LogLevel.Debug);
+ }
+}
diff --git a/src/StardewModdingAPI/LogLevel.cs b/src/StardewModdingAPI/LogLevel.cs
new file mode 100644
index 00000000..89647876
--- /dev/null
+++ b/src/StardewModdingAPI/LogLevel.cs
@@ -0,0 +1,24 @@
+namespace StardewModdingAPI
+{
+ /// The log severity levels.
+ public enum LogLevel
+ {
+ /// Tracing info intended for developers.
+ Trace,
+
+ /// Troubleshooting info that may be relevant to the player.
+ Debug,
+
+ /// Info relevant to the player. This should be used judiciously.
+ Info,
+
+ /// An issue the player should be aware of. This should be used rarely.
+ Warn,
+
+ /// A message indicating something went wrong.
+ Error,
+
+ /// Important information to highlight for the player when player action is needed (e.g. new version available). This should be used rarely to avoid alert fatigue.
+ Alert
+ }
+}
\ No newline at end of file
diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj
index ce302b95..2d6a7c7f 100644
--- a/src/StardewModdingAPI/StardewModdingAPI.csproj
+++ b/src/StardewModdingAPI/StardewModdingAPI.csproj
@@ -195,14 +195,18 @@
+
+
+
+