The Event System says GPAL never writes logs on its own. GPAL.Logger is the built-in, opt-in tool for when you decide you do want a log. One fluent chain configures where entries go and in what format, then every .Log() call writes one more entry there.
GPAL.Logger is a small fluent wrapper that writes whatever you pass to .Log() to a destination you configure once. By default it writes to a file in the current working directory, but WithRootDirectory and WithFilename point it wherever you like. Because writing is done through GPALConverter under the hood, the file extension you give WithFilename decides the output format -- .log or .txt for plain lines, .csv for delimited rows, .json or .xml if you'd rather log structured data. Each entry is automatically prefixed with a timestamp and the name of the method that called .Log(), so you don't have to add that yourself. WithLogType(LogType.INFO | WARNING | ERROR | EXCEPTION) tags the next entry with a severity level -- it applies to a single .Log() call and then resets back to INFO.
GPAL.Logger
.WithRootDirectory(@"C:Logs")
.WithFilename("automation.log")
.Log("Login step completed");
// Tag a single entry as a warning - resets to INFO afterward
GPAL.Logger
.WithRootDirectory(@"C:Logs")
.WithFilename("automation.log")
.WithLogType(LogType.WARNING)
.Log("Retry limit reached, continuing");
Long-running or repeated automations generate a lot of log entries, so GPAL.Logger can split them across files automatically. WithDirectoryStructure builds a date-based folder hierarchy under your root directory (YMD for year/month/day folders, YMDH to add an hour folder, Julian for a day-of-year folder, and several other combinations). The folders are created for you the first time they're needed. WithFilenamePattern appends a timestamp to the filename itself (YMD for one file per day, YMDHMS for one file per run, and so on), so a single configured logger naturally rotates into a new file as time passes.
GPAL.Logger
.WithRootDirectory(@"C:Logs")
.WithDirectoryStructure(DirectoryStructure.YMD)
.WithFilenamePattern(FilenamePattern.YMDHMS)
.WithFilename("automation.log")
.WithLoggingFormat(LogDateFormat.YMD_HMS)
.Log("Run started");
// Writes to, e.g.:
// C:Logs2026 612automation_20260612091500.log
WithLoggingFormat(LogDateFormat) sets the format of the timestamp written at the start of each log entry. WithFilenamePattern is separate. It controls the timestamp appended to the filename for file rotation. The two are independent and commonly used together.
WithDatabase(GPALDatabase) redirects .Log() entirely. Instead of writing to a file, each entry is saved as a row via GPALConverter.SaveTo(database), using whatever table the GPALDatabase object was configured with. This is useful when several processes or machines need to log to one shared, queryable place rather than separate local files.
var auditLog = GPAL.Database
.WithConnectionString("...")
.WithTableName("AutomationLog")
.ToGPALObject();
GPAL.Logger
.WithDatabase(auditLog)
.Log("Invoice batch completed");
If GPAL.Logger can't write an entry (bad path, unreachable database, and so on), it doesn't throw. It reports the problem on the GPAL.InformationHandler/ExceptionHandler channels from The Event System, just like everything else in GPAL.
Showing off some plain text in these paragraphs eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quo veniam mollitia excepturi animi eum illum non libero sapiente provident assumenda, delectus voluptatum nobis sed dolorem adipisci laudantium incidunt. Error, ratione?
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quo veniam mollitia excepturi animi eum illum non libero sapiente provident assumenda, delectus voluptatum nobis sed dolorem adipisci laudantium incidunt. Error, ratione?
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quo veniam mollitia excepturi animi eum illum non libero sapiente provident assumenda, delectus voluptatum nobis sed dolorem adipisci laudantium incidunt. Error, ratione?
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quo veniam mollitia excepturi animi eum illum non libero sapiente provident assumenda, delectus voluptatum nobis sed dolorem adipisci laudantium incidunt. Error, ratione?
Here you can find different accents and emphasis sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
This is a link and how it could look like bestlinkinthebeautifulworld. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Here's just some classic bold text adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam notBoldSecondbestlinkinthebeautifulworld illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Obcaecati, iste distinctio veritatis eligendi laboriosam adipisicing elit illo nostrum corporis at adipisicing elit libero vel voluptas? Expedita, adipisicing facere dolores voluptatem ad ab rem assumenda soluta!
Other cuple of colors in case we want to emphasize several ways adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam adipisicing elit illo nostrum corporis at voluptatem libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta! Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quod veniam, quam ad expedita laborum sed at voluptates culpa ipsam ut vel. Ullam temporibus a mollitia quod aliquam ratione exercitationem nesciunt.
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta! Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quod veniam, quam ad expedita laborum sed at voluptates culpa ipsam ut vel. Ullam temporibus a mollitia quod aliquam ratione exercitationem nesciunt.
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Repudiandae quas consequuntur illo numquam assumenda autem exercitationem distinctio perspiciatis in natus. Eius dicta similique ipsam ipsa minima, nemo quae enim tempore.
GPAL
.CallIfNotFound(GenericCallIfNotFound)
.WithPublishToConsole();
//System.Drawing.Rectangle windowSize = new System.Drawing.Rectangle(10, 10, 1500, 1024);
// NOTE: we have to set browser = before we execute any steps
// this is due to the 'GenericCallIfNotFound' which might throw an exception, and BankScraper will not have the browser set when it calls scraper.Close()
// until the complete fluent line gets executed (meaning every step, meaning browser is not set until everything else succeeds)
browser = GPAL.Browser
.WithBrowserType(Enums.BrowserType.Chrome)
.WithProfileDataDirectory(ChromeProfileLocation)
.WithUseAutomationEngine(AutomationEngine.Selenium)
.WithWindowSize(new System.Drawing.Rectangle(0,0,1920,1080))
.ToGPALObject();