Using Hestia within a Cocoa Application
Let's create a simple Cocoa based example, HestiaTest. The application is designed to provide a simple test
base for checking Hestia development. It forms the basis for checking both a linked Package (from BitBucket) and from local files. It uses a
SwiftUI to generate the user interface. The main structure is (comments have been removed but are retained in the downloadable
version).
HestiaTestApp.swift
import SwiftUI
@main
struct HestiaTestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.fixedSize()
}
.windowResizability(.contentSize)
}
}
Cretae a extension for the LoggerFactory to handle the create of loggers.
Extensions+Logger.swift
import Foundation
import Cocoa
import Hestia
extension LoggerFactory {
static func setLoggingSystem( forClass: String,
file: String = "hestia-configuration.xml",
namespace appNamespace: String = "Hestia" ) -> Logger? {
var masterLoggerFactory: LoggerFactory!
do {
if masterLoggerFactory == nil {
let embeddedConfigPath = Bundle.main.resourcePath!
masterLoggerFactory = LoggerFactory.sharedInstance
masterLoggerFactory.programNamespace = appNamespace
try masterLoggerFactory.configure( from: file,
inFolder: embeddedConfigPath,
using: masterLoggerFactory )
}
return try masterLoggerFactory.getLogger( name: forClass )
} catch let err as LoggerError {
print( err )
return nil
} catch {
print( "Unknown error when setting logging system" )
return nil
}
}
}
The content view provides the main window and two helper functions. First of all provide a list of classes that will be used to excercise
the tester.
ContentView.swift
import SwiftUI
import Hestia
let classList = ["class_1","class_2","class_3","class_4","class_5","class_6"]
Now the helper classes that generate the concurrent and serial tests for the declared test classes.
ContentView.swift
func runSerialClasses( configFileName: String ) {
for name in classList {
let class_n = ChildClass( name: name, using: configFileName )
class_n.generate()
}
}
func runParallelClasses( configFileName: String ) {
for name in classList {
let class_n = ChildClass( name: name, using: configFileName )
let queue = DispatchQueue(label: "\(name) queue", attributes: .concurrent )
queue.async( flags: .barrier ) {
class_n.logger.debug( "Start task: \(name)" )
class_n.generate()
}
}
}
Finally the ContentView View structure
ContentView.swift
struct ContentView: View {
/// The buttons to select an appender to use: console, file, etc
@State var selectedAppender: AppenderButtonsEnum = .console
/// The buttons to select the style of the output: simple, pattern, etc.
@State var selectedLayout: LayoutButtonsEnum = .simple
/// Select to choose how the test classes are run.
@State private var isConcurrent: Bool = false
var body: some View {
VStack {
Text("Hestia Multi-Test")
.font(Font.headline)
.multilineTextAlignment(.leading)
.padding( .top )
// Put the select buttons as two columns.
HStack {
AppendersButtonGroup(selectedAppender: $selectedAppender )
LayoutButtonGroup(selectedLayout: $selectedLayout )
.padding( .bottom )
}
.padding()
VStack( spacing: 10 ) {
Toggle( "Concurrent", isOn: $isConcurrent )
Button( "Run" ) {
if isConcurrent {
Task {
let configFileName = "config-\(selectedLayout)-\(selectedAppender).xml"
runParallelClasses( configFileName: configFileName )
}
} else {
Task {
let configFileName = "config-\(selectedLayout)-\(selectedAppender).xml"
runSerialClasses( configFileName: configFileName )
}
}
}
.padding()
}
}
.padding( .horizontal )
.padding( .bottom )
}
}
This will produce a simple window interface

Finally the test classes are defined.
Classes.swift
import Foundation
import Hestia
class RootClass: NSObject {
public var logger: Logger!
var name: String = ""
init( name className: String, using configName: String ) {
super.init()
self.name = className
logger = LoggerFactory.setLoggingSystem( forClass: className, file: configName )
logger.debug( "Root class \(className) has loaded logger with \(configName)" )
}
func generate() {
logger.debug( "Generate task: \(self.name) " )
for i in 0..<10 {
logger.debug( "\(self.name) loop: \(i)" )
}
}
}
// -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
// -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
class ChildClass: RootClass {
override init( name className: String, using configName: String ) {
super.init( name: className, using: configName )
logger.info( "Initialised \(name)" )
}
}
Configuring Hestia
Using Hestia is relatively simple but it is important to understand how the levels work within the logging system.
Levels define which messages are output. Currently the system uses seven levels (similar to log4java) that are
in a hierarchy of levels:
trace < debug < info < warn < error < fatal < off
If a logger is set to warn then any messages that are marked trace
debug and info will not be output.
It is possible to set and get the logging level from within the application.
Set Level
logger.set( level: "debug" )
Get Level
let currentLevel = logger.getLevel() as LoggerLevel
where loggerLevel is the enumeration:
case trace = 0, debug, info, warn, error, fatal, off
Check Level
if logger.check( level: LoggerLevel.warn ) {
}
The check returns true if the current logging level is less than or equal to the input level.
Build Version
There is a static variable associated which reports the version and build number:
logger.info( "Version: \(LoggerFactory.version)" )
Errors
When HestiaLogger reports errors it throws a LoggerError. The error can be interrogated by using the error
description in the following structure:
do {
// Code that throws LoggerError
} catch let e as LoggerError {
print( e.description )
} catch {
print( "Unknown error" )
}