Repository: NREL/OpenStudio (language: python)

Issue:
4847 logger singleton 3.0
@jmarrec I think this should address the core issue that the logger is not global across the script engines. I think there remains a little bit of work.

1. I'm going to rename LoggerSingleton since it is not really a singleton (Logger is) and the name is confusing. Also, LoggerSingleton is not part of the public API so I think we can change it without causing harm.
2. I think we might be able to remove the define `SHARED_OS_LIBS`. What do you think?
3. Now that I believe we have the global issue worked out, do you have availability to go through the CLI's use of the logger and undo any workarounds that might be in place? I believe there are some `if Windows` conditions that attempted to work around the issue.
OpenStudio logging singleton issues (especially on Windows) when using interpreted bindings (ruby measures)
Issue overview
--------------

Two issues observed comparing the behavior of OpenStudio logging between Linux and Windows:

1. Windows produces more warning messages than appear on Linux. Why?! I primarily do my testing on Linux, so I'm concerned that I might be missing things.

```patch
--- C:/Users/shorowit/Desktop/Linux.txt	Thu Mar 30 15:24:05 2023
+++ C:/Users/shorowit/Desktop/Windows.txt	Thu Mar 30 15:23:57 2023
@@ -3,2 +3,5 @@
 [openstudio.measure.OSRunner] <1> Cannot find current Workflow Step
+[openstudio.measure.OSRunner] <1> Cannot find current Workflow Step
+[openstudio.EpwFile] <0> Successive data points (1995-Jan-31 to 1994-Feb-01, ending on line 753) are greater than 1 day apart in EPW file 'C:/git/openstudio-hpxml/HPXMLtoOpenStudio/resources/../../weather/USA_CO_Denver.Intl.AP.725650_TMY3.epw'. Data will be treated as typical (TMY)
+[openstudio.WorkflowStepResult] <0> WorkflowStepResult value called with undefined stepResult, returning 'Success'
 Running simulation...
@@ -8,2 +11,6 @@
 [openstudio.measure.OSRunner] <1> Cannot find current Workflow Step
+[openstudio.WorkflowStepResult] <0> WorkflowStepResult value called with undefined stepResult, returning 'Success'
+[openstudio.measure.OSRunner] <1> Cannot find current Workflow Step
+[openstudio.measure.OSRunner] <1> Cannot find current Workflow Step
+[openstudio.WorkflowStepResult] <0> WorkflowStepResult value called with undefined stepResult, returning 'Success'
 Wrote output file: run/results_annual.csv.
```

2. When using `OpenStudio::Logger.instance.standardOutLogger.setLogLevel(OpenStudio::Fatal)` to suppress warning messages, warnings are completely suppressed on Linux but not on Windows. For some of our workflows, this means we can get dozens and dozens of lines of output on Windows, which is annoying and makes it difficult to see the output of interest.

```patch
--- C:/Users/shorowit/Desktop/Linux.txt	Thu Mar 30 15:24:02 2023
+++ C:/Users/shorowit/Desktop/Windows.txt	Thu Mar 30 15:24:00 2023
@@ -2,2 +2,5 @@
 Creating input...
+[openstudio.measure.OSRunner] <1> Cannot find current Workflow Step
+[openstudio.EpwFile] <0> Successive data points (1995-Jan-31 to 1994-Feb-01, ending on line 753) are greater than 1 day apart in EPW file 'C:/git/openstudio-hpxml/HPXMLtoOpenStudio/resources/../../weather/USA_CO_Denver.Intl.AP.725650_TMY3.epw'. Data will be treated as typical (TMY)
+[openstudio.WorkflowStepResult] <0> WorkflowStepResult value called with undefined stepResult, returning 'Success'
 Running simulation...
@@ -5,2 +8,6 @@
 Processing output...
+[openstudio.measure.OSRunner] <1> Cannot find current Workflow Step
+[openstudio.WorkflowStepResult] <0> WorkflowStepResult value called with undefined stepResult, returning 'Success'
+[openstudio.measure.OSRunner] <1> Cannot find current Workflow Step
+[openstudio.WorkflowStepResult] <0> WorkflowStepResult value called with undefined stepResult, returning 'Success'
 Wrote output file: run/results_annual.csv.

```

## Expected Behavior
OpenStudio should behave the same on Linux and Windows.

## Steps to Reproduce
1. Clone https://github.com/NREL/OpenStudio-HPXML
2. To reproduce issue 1, run `openstudio workflow/run_simulation.rb -x workflow/sample_files/base.xml -o test` on Windows and Linux.
3. To reproduce issue 2, comment out [this line](https://github.com/NREL/OpenStudio-HPXML/blob/4f39a733f647d7b7366b176bc66925845b91de20/HPXMLtoOpenStudio/resources/meta_measure.rb#L15) and run the above command again on Windows and Linux.

## Details

### Environment

Some additional details about your environment for this issue (if relevant):
 - Windows 10 and Ubuntu 20.04 (WSL)
 - OpenStudio 3.5
OpenStudio logging singleton issues (especially on Windows) when using interpreted bindings (ruby measures)
Issue overview
--------------

Two issues observed comparing the behavior of OpenStudio logging between Linux and Windows:

1. Windows produces more warning messages than appear on Linux. Why?! I primarily do my testing on Linux, so I'm concerned that I might be missing things.

```patch
--- C:/Users/shorowit/Desktop/Linux.txt	Thu Mar 30 15:24:05 2023
+++ C:/Users/shorowit/Desktop/Windows.txt	Thu Mar 30 15:23:57 2023
@@ -3,2 +3,5 @@
 [openstudio.measure.OSRunner] <1> Cannot find current Workflow Step
+[openstudio.measure.OSRunner] <1> Cannot find current Workflow Step
+[openstudio.EpwFile] <0> Successive data points (1995-Jan-31 to 1994-Feb-01, ending on line 753) are greater than 1 day apart in EPW file 'C:/git/openstudio-hpxml/HPXMLtoOpenStudio/resources/../../weather/USA_CO_Denver.Intl.AP.725650_TMY3.epw'. Data will be treated as typical (TMY)
+[openstudio.WorkflowStepResult] <0> WorkflowStepResult value called with undefined stepResult, returning 'Success'
 Running simulation...
@@ -8,2 +11,6 @@
 [openstudio.measure.OSRunner] <1> Cannot find current Workflow Step
+[openstudio.WorkflowStepResult] <0> WorkflowStepResult value called with undefined stepResult, returning 'Success'
+[openstudio.measure.OSRunner] <1> Cannot find current Workflow Step
+[openstudio.measure.OSRunner] <1> Cannot find current Workflow Step
+[openstudio.WorkflowStepResult] <0> WorkflowStepResult value called with undefined stepResult, returning 'Success'
 Wrote output file: run/results_annual.csv.
```

2. When using `OpenStudio::Logger.instance.standardOutLogger.setLogLevel(OpenStudio::Fatal)` to suppress warning messages, warnings are completely suppressed on Linux but not on Windows. For some of our workflows, this means we can get dozens and dozens of lines of output on Windows, which is annoying and makes it difficult to see the output of interest.

```patch
--- C:/Users/shorowit/Desktop/Linux.txt	Thu Mar 30 15:24:02 2023
+++ C:/Users/shorowit/Desktop/Windows.txt	Thu Mar 30 15:24:00 2023
@@ -2,2 +2,5 @@
 Creating input...
+[openstudio.measure.OSRunner] <1> Cannot find current Workflow Step
+[openstudio.EpwFile] <0> Successive data points (1995-Jan-31 to 1994-Feb-01, ending on line 753) are greater than 1 day apart in EPW file 'C:/git/openstudio-hpxml/HPXMLtoOpenStudio/resources/../../weather/USA_CO_Denver.Intl.AP.725650_TMY3.epw'. Data will be treated as typical (TMY)
+[openstudio.WorkflowStepResult] <0> WorkflowStepResult value called with undefined stepResult, returning 'Success'
 Running simulation...
@@ -5,2 +8,6 @@
 Processing output...
+[openstudio.measure.OSRunner] <1> Cannot find current Workflow Step
+[openstudio.WorkflowStepResult] <0> WorkflowStepResult value called with undefined stepResult, returning 'Success'
+[openstudio.measure.OSRunner] <1> Cannot find current Workflow Step
+[openstudio.WorkflowStepResult] <0> WorkflowStepResult value called with undefined stepResult, returning 'Success'
 Wrote output file: run/results_annual.csv.

```

## Expected Behavior
OpenStudio should behave the same on Linux and Windows.

## Steps to Reproduce
1. Clone https://github.com/NREL/OpenStudio-HPXML
2. To reproduce issue 1, run `openstudio workflow/run_simulation.rb -x workflow/sample_files/base.xml -o test` on Windows and Linux.
3. To reproduce issue 2, comment out [this line](https://github.com/NREL/OpenStudio-HPXML/blob/4f39a733f647d7b7366b176bc66925845b91de20/HPXMLtoOpenStudio/resources/meta_measure.rb#L15) and run the above command again on Windows and Linux.

## Details

### Environment

Some additional details about your environment for this issue (if relevant):
 - Windows 10 and Ubuntu 20.04 (WSL)
 - OpenStudio 3.5
OpenStudio logging singleton issues (especially on Windows) when using interpreted bindings (ruby measures)
Issue overview
--------------

Two issues observed comparing the behavior of OpenStudio logging between Linux and Windows:

1. Windows produces more warning messages than appear on Linux. Why?! I primarily do my testing on Linux, so I'm concerned that I might be missing things.

```patch
--- C:/Users/shorowit/Desktop/Linux.txt	Thu Mar 30 15:24:05 2023
+++ C:/Users/shorowit/Desktop/Windows.txt	Thu Mar 30 15:23:57 2023
@@ -3,2 +3,5 @@
 [openstudio.measure.OSRunner] <1> Cannot find current Workflow Step
+[openstudio.measure.OSRunner] <1> Cannot find current Workflow Step
+[openstudio.EpwFile] <0> Successive data points (1995-Jan-31 to 1994-Feb-01, ending on line 753) are greater than 1 day apart in EPW file 'C:/git/openstudio-hpxml/HPXMLtoOpenStudio/resources/../../weather/USA_CO_Denver.Intl.AP.725650_TMY3.epw'. Data will be treated as typical (TMY)
+[openstudio.WorkflowStepResult] <0> WorkflowStepResult value called with undefined stepResult, returning 'Success'
 Running simulation...
@@ -8,2 +11,6 @@
 [openstudio.measure.OSRunner] <1> Cannot find current Workflow Step
+[openstudio.WorkflowStepResult] <0> WorkflowStepResult value called with undefined stepResult, returning 'Success'
+[openstudio.measure.OSRunner] <1> Cannot find current Workflow Step
+[openstudio.measure.OSRunner] <1> Cannot find current Workflow Step
+[openstudio.WorkflowStepResult] <0> WorkflowStepResult value called with undefined stepResult, returning 'Success'
 Wrote output file: run/results_annual.csv.
```

2. When using `OpenStudio::Logger.instance.standardOutLogger.setLogLevel(OpenStudio::Fatal)` to suppress warning messages, warnings are completely suppressed on Linux but not on Windows. For some of our workflows, this means we can get dozens and dozens of lines of output on Windows, which is annoying and makes it difficult to see the output of interest.

```patch
--- C:/Users/shorowit/Desktop/Linux.txt	Thu Mar 30 15:24:02 2023
+++ C:/Users/shorowit/Desktop/Windows.txt	Thu Mar 30 15:24:00 2023
@@ -2,2 +2,5 @@
 Creating input...
+[openstudio.measure.OSRunner] <1> Cannot find current Workflow Step
+[openstudio.EpwFile] <0> Successive data points (1995-Jan-31 to 1994-Feb-01, ending on line 753) are greater than 1 day apart in EPW file 'C:/git/openstudio-hpxml/HPXMLtoOpenStudio/resources/../../weather/USA_CO_Denver.Intl.AP.725650_TMY3.epw'. Data will be treated as typical (TMY)
+[openstudio.WorkflowStepResult] <0> WorkflowStepResult value called with undefined stepResult, returning 'Success'
 Running simulation...
@@ -5,2 +8,6 @@
 Processing output...
+[openstudio.measure.OSRunner] <1> Cannot find current Workflow Step
+[openstudio.WorkflowStepResult] <0> WorkflowStepResult value called with undefined stepResult, returning 'Success'
+[openstudio.measure.OSRunner] <1> Cannot find current Workflow Step
+[openstudio.WorkflowStepResult] <0> WorkflowStepResult value called with undefined stepResult, returning 'Success'
 Wrote output file: run/results_annual.csv.

```

## Expected Behavior
OpenStudio should behave the same on Linux and Windows.

## Steps to Reproduce
1. Clone https://github.com/NREL/OpenStudio-HPXML
2. To reproduce issue 1, run `openstudio workflow/run_simulation.rb -x workflow/sample_files/base.xml -o test` on Windows and Linux.
3. To reproduce issue 2, comment out [this line](https://github.com/NREL/OpenStudio-HPXML/blob/4f39a733f647d7b7366b176bc66925845b91de20/HPXMLtoOpenStudio/resources/meta_measure.rb#L15) and run the above command again on Windows and Linux.

## Details

### Environment

Some additional details about your environment for this issue (if relevant):
 - Windows 10 and Ubuntu 20.04 (WSL)
 - OpenStudio 3.5


Discussion / hints:

Oh great, another one of the "Windows" v "Linux" difference type :)

Any chance you have a MCVE that doesn't involve a complex gem like OpenStudio-HPXML? Something like `openstudio run -w in.osw` with a minimal model.cat 

FWIW, the EPW you use is supposed to issue a 0 = OpenStudio::Warn log message

Using Ubuntu 20.04, in pry:

```ruby
epw = OpenStudio::EpwFile.new('weather/USA_CO_Denver.Intl.AP.725650_TMY3.epw')
[openstudio.EpwFile] <0> Successive data points (1995-Jan-31 to 1994-Feb-01, ending on line 753) are greater than 1 day apart in EPW file 'weather/USA_CO_Denver.Intl.AP.725650_TMY3.epw'. Data will be treated as typical (TMY)
```
Well actually I can reproduce using only the compact_ruby_only.osw we have in source.

```shell
(py39)julien@compact_osw$ $os_build_rel3/Products/openstudio run -w compact_ruby_only.osw
[11:59:34.530107 WARN] Measure couldn't find the construction named 'EXTERIOR-WALL adj exterior wall insulation' in the exterior surface hash.
(py39)julien@compact_osw$ cat run/run.log 
[11:59:34.530107 WARN] Measure couldn't find the construction named 'EXTERIOR-WALL adj exterior wall insulation' in the exterior surface hash
```

```shell
$ C:\src\OpenStudio\build-release\Products\openstudio.exe run -w compact_ruby_only.osw
[OpenStudio.Measure] <0> OSRunner is deprecated, use OpenStudio::Measure::OSRunner instead.
[openstudio.EpwFile] <0> Header start day of the week and actual start day of the week do not match in EPW file 'C:/src/OpenStudio/build-debug/resources/Examples/compact_osw/files/srrl_2013_amy.epw'. Data will be treated as typical (TMY)
[OpenStudio.Measure] <0> OSArgumentMap is deprecated, use OpenStudio::Measure::OSArgumentMap instead.
[OpenStudio.Measure] <0> OSArgumentMap is deprecated, use OpenStudio::Measure::OSArgumentMap instead.
[12:04:09.179426 WARN] Measure couldn't find the construction named 'EXTERIOR-WALL adj exterior wall insulation' in the exterior surface hash.
[openstudio.energyplus.ForwardTranslator] <0> OS_ProgramControl is not currently translated
[utilities.idf.WorkspaceObject] <0> Object of type 'Schedule:Constant' and named 'Always Off Discrete', points to an object named OnOff from field 1, but that object cannot be located.
[utilities.idf.WorkspaceObject] <0> Object of type 'Schedule:Constant' and named 'Always On Continuous', points to an object named Fractional 1 from field 1, but that object cannot be located.
[OpenStudio.Measure] <0> OSArgumentMap is deprecated, use OpenStudio::Measure::OSArgumentMap instead.
[OpenStudio.Measure] <0> OSArgumentMap is deprecated, use OpenStudio::Measure::OSArgumentMap instead.
[OpenStudio.Measure] <0> OSArgumentMap is deprecated, use OpenStudio::Measure::OSArgumentMap instead.
$  cat .\run\run.log
[12:04:09.179426 WARN] Measure couldn't find the construction named 'EXTERIOR-WALL adj exterior wall insulation' in the exterior surface hash.
```

The `labs` doesn't have the same issue (but I realize it outputs everything to run.log though), so this is likely a ruby-side issue. I'll look into it
https://github.com/NREL/OpenStudio/blob/33626993b6315a614eb67de7b158bf4e5fb384d9/ruby/engine/openstudio_cli.rb#L49-L56
Ok, here is a proper MCVE:

```ruby
$logger = Logger.new(STDOUT)
$logger.level = Logger::WARN

OpenStudio::Logger.instance.standardOutLogger.setLogLevel(OpenStudio::Error)

$logger.info "STDOUT Info"
$logger.warn "STDOUT Warn"
$logger.error "STDOUT Error"

OpenStudio::logFree(OpenStudio::Info, "test", "Info")
OpenStudio::logFree(OpenStudio::Warn, "test", "Warn")
OpenStudio::logFree(OpenStudio::Error, "test", "Error")
```

Unix behaves correctly:

```shell
$ openstudio test.rb
W, [2023-04-04T12:21:56.062121 #230289]  WARN -- : STDOUT Warn
E, [2023-04-04T12:21:56.062161 #230289] ERROR -- : STDOUT Error
[test] <1> Error
```

Windows shows the issue. Seems like both the stdout logger and the SDK logger are intertwined:

```shell
$ openstudio.exe .\test.rb
W, [2023-04-04T12:22:32.765676 #15076]  WARN -- : STDOUT Warn
E, [2023-04-04T12:22:32.766081 #15076] ERROR -- : STDOUT Error
[test] <0> Warn
[test] <1> Error
[test] <1> Error
```
And here we are... We broke something during the `labs` work

```shell
$ C:\Users\julien\Downloads\OpenStudio-3.4.0+9e989dbeea-Windows\bin\openstudio.exe .\test.rb
W, [2023-04-04T12:33:42.759688 #14548]  WARN -- : STDOUT Warn
E, [2023-04-04T12:33:42.760148 #14548] ERROR -- : STDOUT Error
[test] <1> Error
$ C:\Users\julien\Downloads\OpenStudio-3.5.0+7b14ce1588-Windows\bin\openstudio.exe .\test.rb
W, [2023-04-04T12:34:13.628220 #11696]  WARN -- : STDOUT Warn
E, [2023-04-04T12:34:13.628766 #11696] ERROR -- : STDOUT Error
[test] <0> Warn
[test] <1> Error
[test] <1> Error
```

@kbenne My money is on the use of a shared lib for ruby: https://github.com/NREL/OpenStudio/commit/50f6c0378ce1e5e4e48c525d7d1e2e80fd6adcb2 . Thoughts?

I think we basically have one logger in the libopenstudiolib and ANOTHER one in the ruby shared lib.
The Ruby shared lib one is correctly set to log level = Error, but the other one is still set to the default of Warn. https://github.com/NREL/OpenStudio/blob/33626993b6315a614eb67de7b158bf4e5fb384d9/src/utilities/core/Logger.cpp#L54

Both are triggered... which explains why issuing an Info shows 0 message, Warn shows 1 message, and anything Error or above shows 2 messages.
On windows, if here I modify this line: https://github.com/NREL/OpenStudio/blob/33626993b6315a614eb67de7b158bf4e5fb384d9/src/cli/main.cpp#L299

to be

```diff
+   // Disable this logger, we have a duplicate in the ruby shared lib
+   openstudio::Logger::instance().standardOutLogger().disable();
+   openstudio::Logger::instance().standardErrLogger().disable();
    result = openstudio::rubyCLI(rubyEngine);
```

Then past the initial extra messages issued when we locate the DLL via getOpenStudioModule, I get only one set of messages

```shell
$ Products\openstudio.exe execute_ruby_script .\resources\Examples\compact_osw\test.rb
[2023-04-04 13:14:08.970602] [0x00003a00] [info]    getOpenStudioModule, szPath = 'C:\src\OpenStudio\build-debug\Products\openstudiolib.dll'
[2023-04-04 13:14:08.973631] [0x00003a00] [info]    completeAndNormalize: looking for p = C:/src/OpenStudio/build-debug/Products/openstudiolib.dll
[2023-04-04 13:14:08.974637] [0x00003a00] [info]    completeAndNormalize: temp = C:/src/OpenStudio/build-debug/Products/openstudiolib.dll
[2023-04-04 13:14:08.981796] [0x00003a00] [info]    completeAndNormalize: result = C:/src/OpenStudio/build-debug/Products/openstudiolib.dll
[2023-04-04 13:14:08.982718] [0x00003a00] [info]    getOpenStudioModule, found 'C:/src/OpenStudio/build-debug/Products/openstudiolib.dll'

W, [2023-04-04T13:14:13.052013 #6864]  WARN -- : STDOUT Warn
E, [2023-04-04T13:14:13.052447 #6864] ERROR -- : STDOUT Error
[test] <1> Error
```
@jmarrec You had fixed this issue, but it's no longer working for Windows w/ OS 3.7. You can easily reproduce the issue by cloning OpenStudio-HPXML and running `openstudio tasks.rb update_hpxmls`. We are using `OpenStudio::Logger.instance.standardOutLogger.setLogLevel(OpenStudio::Fatal)` to try to suppress messages.

Linux w/ C++ CLI:
![image](https://github.com/NREL/OpenStudio/assets/5861765/51662530-0150-4bb0-a81e-43f6cdb70029)

Windows w/ C++ CLI:
![image](https://github.com/NREL/OpenStudio/assets/5861765/39f4609f-5af9-4137-9126-f5a41800162c)

I was doing some more testing and realized this broke w/ the switch to the C++ CLI. It's easy to confirm because you can use the 3.7.0 release of OpenStudio and run the above command using `classic` or not. When running with classic the standardOutLogger's log level is correctly used (see below); when running w/o classic, it is not (see above).

Windows w/ classic CLI:
![image](https://github.com/NREL/OpenStudio/assets/5861765/6aea2660-b06d-4ea6-8355-6595f28a818f)

@shorowit I guess it was misleading having closed this issue. What I did was a mere workaround for the "classic" version in https://github.com/NREL/OpenStudio/pull/4852/commits/83015445b16e5032289600c2b723d4d0ce0f415a .
We've had a failing test since for the labs version.

The issue is that the Logger is a Static Singleton. And We get one in the libopenstudiolib, but we also get one in the rubyengine and one in the pythonengine (which are `dlopen`ed). So we end up with several logger instances... I don't know how to resolve that situation yet, and I reckon we'll need @kbenne 's magic touch for this.
Oh great, another one of the "Windows" v "Linux" difference type :)

Any chance you have a MCVE that doesn't involve a complex gem like OpenStudio-HPXML? Something like `openstudio run -w in.osw` with a minimal model.cat 

FWIW, the EPW you use is supposed to issue a 0 = OpenStudio::Warn log message

Using Ubuntu 20.04, in pry:

```ruby
epw = OpenStudio::EpwFile.new('weather/USA_CO_Denver.Intl.AP.725650_TMY3.epw')
[openstudio.EpwFile] <0> Successive data points (1995-Jan-31 to 1994-Feb-01, ending on line 753) are greater than 1 day apart in EPW file 'weather/USA_CO_Denver.Intl.AP.725650_TMY3.epw'. Data will be treated as typical (TMY)
```
Well actually I can reproduce using only the compact_ruby_only.osw we have in source.

```shell
(py39)julien@compact_osw$ $os_build_rel3/Products/openstudio run -w compact_ruby_only.osw
[11:59:34.530107 WARN] Measure couldn't find the construction named 'EXTERIOR-WALL adj exterior wall insulation' in the exterior surface hash.
(py39)julien@compact_osw$ cat run/run.log 
[11:59:34.530107 WARN] Measure couldn't find the construction named 'EXTERIOR-WALL adj exterior wall insulation' in the exterior surface hash
```

```shell
$ C:\src\OpenStudio\build-release\Products\openstudio.exe run -w compact_ruby_only.osw
[OpenStudio.Measure] <0> OSRunner is deprecated, use OpenStudio::Measure::OSRunner instead.
[openstudio.EpwFile] <0> Header start day of the week and actual start day of the week do not match in EPW file 'C:/src/OpenStudio/build-debug/resources/Examples/compact_osw/files/srrl_2013_amy.epw'. Data will be treated as typical (TMY)
[OpenStudio.Measure] <0> OSArgumentMap is deprecated, use OpenStudio::Measure::OSArgumentMap instead.
[OpenStudio.Measure] <0> OSArgumentMap is deprecated, use OpenStudio::Measure::OSArgumentMap instead.
[12:04:09.179426 WARN] Measure couldn't find the construction named 'EXTERIOR-WALL adj exterior wall insulation' in the exterior surface hash.
[openstudio.energyplus.ForwardTranslator] <0> OS_ProgramControl is not currently translated
[utilities.idf.WorkspaceObject] <0> Object of type 'Schedule:Constant' and named 'Always Off Discrete', points to an object named OnOff from field 1, but that object cannot be located.
[utilities.idf.WorkspaceObject] <0> Object of type 'Schedule:Constant' and named 'Always On Continuous', points to an object named Fractional 1 from field 1, but that object cannot be located.
[OpenStudio.Measure] <0> OSArgumentMap is deprecated, use OpenStudio::Measure::OSArgumentMap instead.
[OpenStudio.Measure] <0> OSArgumentMap is deprecated, use OpenStudio::Measure::OSArgumentMap instead.
[OpenStudio.Measure] <0> OSArgumentMap is deprecated, use OpenStudio::Measure::OSArgumentMap instead.
$  cat .\run\run.log
[12:04:09.179426 WARN] Measure couldn't find the construction named 'EXTERIOR-WALL adj exterior wall insulation' in the exterior surface hash.
```

The `labs` doesn't have the same issue (but I realize it outputs everything to run.log though), so this is likely a ruby-side issue. I'll look into it
https://github.com/NREL/OpenStudio/blob/33626993b6315a614eb67de7b158bf4e5fb384d9/ruby/engine/openstudio_cli.rb#L49-L56
Ok, here is a proper MCVE:

```ruby
$logger = Logger.new(STDOUT)
$logger.level = Logger::WARN

OpenStudio::Logger.instance.standardOutLogger.setLogLevel(OpenStudio::Error)

$logger.info "STDOUT Info"
$logger.warn "STDOUT Warn"
$logger.error "STDOUT Error"

OpenStudio::logFree(OpenStudio::Info, "test", "Info")
OpenStudio::logFree(OpenStudio::Warn, "test", "Warn")
OpenStudio::logFree(OpenStudio::Error, "test", "Error")
```

Unix behaves correctly:

```shell
$ openstudio test.rb
W, [2023-04-04T12:21:56.062121 #230289]  WARN -- : STDOUT Warn
E, [2023-04-04T12:21:56.062161 #230289] ERROR -- : STDOUT Error
[test] <1> Error
```

Windows shows the issue. Seems like both the stdout logger and the SDK logger are intertwined:

```shell
$ openstudio.exe .\test.rb
W, [2023-04-04T12:22:32.765676 #15076]  WARN -- : STDOUT Warn
E, [2023-04-04T12:22:32.766081 #15076] ERROR -- : STDOUT Error
[test] <0> Warn
[test] <1> Error
[test] <1> Error
```
And here we are... We broke something during the `labs` work

```shell
$ C:\Users\julien\Downloads\OpenStudio-3.4.0+9e989dbeea-Windows\bin\openstudio.exe .\test.rb
W, [2023-04-04T12:33:42.759688 #14548]  WARN -- : STDOUT Warn
E, [2023-04-04T12:33:42.760148 #14548] ERROR -- : STDOUT Error
[test] <1> Error
$ C:\Users\julien\Downloads\OpenStudio-3.5.0+7b14ce1588-Windows\bin\openstudio.exe .\test.rb
W, [2023-04-04T12:34:13.628220 #11696]  WARN -- : STDOUT Warn
E, [2023-04-04T12:34:13.628766 #11696] ERROR -- : STDOUT Error
[test] <0> Warn
[test] <1> Error
[test] <1> Error
```

@kbenne My money is on the use of a shared lib for ruby: https://github.com/NREL/OpenStudio/commit/50f6c0378ce1e5e4e48c525d7d1e2e80fd6adcb2 . Thoughts?

I think we basically have one logger in the libopenstudiolib and ANOTHER one in the ruby shared lib.
The Ruby shared lib one is correctly set to log level = Error, but the other one is still set to the default of Warn. https://github.com/NREL/OpenStudio/blob/33626993b6315a614eb67de7b158bf4e5fb384d9/src/utilities/core/Logger.cpp#L54

Both are triggered... which explains why issuing an Info shows 0 message, Warn shows 1 message, and anything Error or above shows 2 messages.
On windows, if here I modify this line: https://github.com/NREL/OpenStudio/blob/33626993b6315a614eb67de7b158bf4e5fb384d9/src/cli/main.cpp#L299

to be

```diff
+   // Disable this logger, we have a duplicate in the ruby shared lib
+   openstudio::Logger::instance().standardOutLogger().disable();
+   openstudio::Logger::instance().standardErrLogger().disable();
    result = openstudio::rubyCLI(rubyEngine);
```

Then past the initial extra messages issued when we locate the DLL via getOpenStudioModule, I get only one set of messages

```shell
$ Products\openstudio.exe execute_ruby_script .\resources\Examples\compact_osw\test.rb
[2023-04-04 13:14:08.970602] [0x00003a00] [info]    getOpenStudioModule, szPath = 'C:\src\OpenStudio\build-debug\Products\openstudiolib.dll'
[2023-04-04 13:14:08.973631] [0x00003a00] [info]    completeAndNormalize: looking for p = C:/src/OpenStudio/build-debug/Products/openstudiolib.dll
[2023-04-04 13:14:08.974637] [0x00003a00] [info]    completeAndNormalize: temp = C:/src/OpenStudio/build-debug/Products/openstudiolib.dll
[2023-04-04 13:14:08.981796] [0x00003a00] [info]    completeAndNormalize: result = C:/src/OpenStudio/build-debug/Products/openstudiolib.dll
[2023-04-04 13:14:08.982718] [0x00003a00] [info]    getOpenStudioModule, found 'C:/src/OpenStudio/build-debug/Products/openstudiolib.dll'

W, [2023-04-04T13:14:13.052013 #6864]  WARN -- : STDOUT Warn
E, [2023-04-04T13:14:13.052447 #6864] ERROR -- : STDOUT Error
[test] <1> Error
```
@jmarrec You had fixed this issue, but it's no longer working for Windows w/ OS 3.7. You can easily reproduce the issue by cloning OpenStudio-HPXML and running `openstudio tasks.rb update_hpxmls`. We are using `OpenStudio::Logger.instance.standardOutLogger.setLogLevel(OpenStudio::Fatal)` to try to suppress messages.

Linux w/ C++ CLI:
![image](https://github.com/NREL/OpenStudio/assets/5861765/51662530-0150-4bb0-a81e-43f6cdb70029)

Windows w/ C++ CLI:
![image](https://github.com/NREL/OpenStudio/assets/5861765/39f4609f-5af9-4137-9126-f5a41800162c)

I was doing some more testing and realized this broke w/ the switch to the C++ CLI. It's easy to confirm because you can use the 3.7.0 release of OpenStudio and run the above command using `classic` or not. When running with classic the standardOutLogger's log level is correctly used (see below); when running w/o classic, it is not (see above).

Windows w/ classic CLI:
![image](https://github.com/NREL/OpenStudio/assets/5861765/6aea2660-b06d-4ea6-8355-6595f28a818f)

@shorowit I guess it was misleading having closed this issue. What I did was a mere workaround for the "classic" version in https://github.com/NREL/OpenStudio/pull/4852/commits/83015445b16e5032289600c2b723d4d0ce0f415a .
We've had a failing test since for the labs version.

The issue is that the Logger is a Static Singleton. And We get one in the libopenstudiolib, but we also get one in the rubyengine and one in the pythonengine (which are `dlopen`ed). So we end up with several logger instances... I don't know how to resolve that situation yet, and I reckon we'll need @kbenne 's magic touch for this.
Oh great, another one of the "Windows" v "Linux" difference type :)

Any chance you have a MCVE that doesn't involve a complex gem like OpenStudio-HPXML? Something like `openstudio run -w in.osw` with a minimal model.cat 

FWIW, the EPW you use is supposed to issue a 0 = OpenStudio::Warn log message

Using Ubuntu 20.04, in pry:

```ruby
epw = OpenStudio::EpwFile.new('weather/USA_CO_Denver.Intl.AP.725650_TMY3.epw')
[openstudio.EpwFile] <0> Successive data points (1995-Jan-31 to 1994-Feb-01, ending on line 753) are greater than 1 day apart in EPW file 'weather/USA_CO_Denver.Intl.AP.725650_TMY3.epw'. Data will be treated as typical (TMY)
```
Well actually I can reproduce using only the compact_ruby_only.osw we have in source.

```shell
(py39)julien@compact_osw$ $os_build_rel3/Products/openstudio run -w compact_ruby_only.osw
[11:59:34.530107 WARN] Measure couldn't find the construction named 'EXTERIOR-WALL adj exterior wall insulation' in the exterior surface hash.
(py39)julien@compact_osw$ cat run/run.log 
[11:59:34.530107 WARN] Measure couldn't find the construction named 'EXTERIOR-WALL adj exterior wall insulation' in the exterior surface hash
```

```shell
$ C:\src\OpenStudio\build-release\Products\openstudio.exe run -w compact_ruby_only.osw
[OpenStudio.Measure] <0> OSRunner is deprecated, use OpenStudio::Measure::OSRunner instead.
[openstudio.EpwFile] <0> Header start day of the week and actual start day of the week do not match in EPW file 'C:/src/OpenStudio/build-debug/resources/Examples/compact_osw/files/srrl_2013_amy.epw'. Data will be treated as typical (TMY)
[OpenStudio.Measure] <0> OSArgumentMap is deprecated, use OpenStudio::Measure::OSArgumentMap instead.
[OpenStudio.Measure] <0> OSArgumentMap is deprecated, use OpenStudio::Measure::OSArgumentMap instead.
[12:04:09.179426 WARN] Measure couldn't find the construction named 'EXTERIOR-WALL adj exterior wall insulation' in the exterior surface hash.
[openstudio.energyplus.ForwardTranslator] <0> OS_ProgramControl is not currently translated
[utilities.idf.WorkspaceObject] <0> Object of type 'Schedule:Constant' and named 'Always Off Discrete', points to an object named OnOff from field 1, but that object cannot be located.
[utilities.idf.WorkspaceObject] <0> Object of type 'Schedule:Constant' and named 'Always On Continuous', points to an object named Fractional 1 from field 1, but that object cannot be located.
[OpenStudio.Measure] <0> OSArgumentMap is deprecated, use OpenStudio::Measure::OSArgumentMap instead.
[OpenStudio.Measure] <0> OSArgumentMap is deprecated, use OpenStudio::Measure::OSArgumentMap instead.
[OpenStudio.Measure] <0> OSArgumentMap is deprecated, use OpenStudio::Measure::OSArgumentMap instead.
$  cat .\run\run.log
[12:04:09.179426 WARN] Measure couldn't find the construction named 'EXTERIOR-WALL adj exterior wall insulation' in the exterior surface hash.
```

The `labs` doesn't have the same issue (but I realize it outputs everything to run.log though), so this is likely a ruby-side issue. I'll look into it
https://github.com/NREL/OpenStudio/blob/33626993b6315a614eb67de7b158bf4e5fb384d9/ruby/engine/openstudio_cli.rb#L49-L56
Ok, here is a proper MCVE:

```ruby
$logger = Logger.new(STDOUT)
$logger.level = Logger::WARN

OpenStudio::Logger.instance.standardOutLogger.setLogLevel(OpenStudio::Error)

$logger.info "STDOUT Info"
$logger.warn "STDOUT Warn"
$logger.error "STDOUT Error"

OpenStudio::logFree(OpenStudio::Info, "test", "Info")
OpenStudio::logFree(OpenStudio::Warn, "test", "Warn")
OpenStudio::logFree(OpenStudio::Error, "test", "Error")
```

Unix behaves correctly:

```shell
$ openstudio test.rb
W, [2023-04-04T12:21:56.062121 #230289]  WARN -- : STDOUT Warn
E, [2023-04-04T12:21:56.062161 #230289] ERROR -- : STDOUT Error
[test] <1> Error
```

Windows shows the issue. Seems like both the stdout logger and the SDK logger are intertwined:

```shell
$ openstudio.exe .\test.rb
W, [2023-04-04T12:22:32.765676 #15076]  WARN -- : STDOUT Warn
E, [2023-04-04T12:22:32.766081 #15076] ERROR -- : STDOUT Error
[test] <0> Warn
[test] <1> Error
[test] <1> Error
```
And here we are... We broke something during the `labs` work

```shell
$ C:\Users\julien\Downloads\OpenStudio-3.4.0+9e989dbeea-Windows\bin\openstudio.exe .\test.rb
W, [2023-04-04T12:33:42.759688 #14548]  WARN -- : STDOUT Warn
E, [2023-04-04T12:33:42.760148 #14548] ERROR -- : STDOUT Error
[test] <1> Error
$ C:\Users\julien\Downloads\OpenStudio-3.5.0+7b14ce1588-Windows\bin\openstudio.exe .\test.rb
W, [2023-04-04T12:34:13.628220 #11696]  WARN -- : STDOUT Warn
E, [2023-04-04T12:34:13.628766 #11696] ERROR -- : STDOUT Error
[test] <0> Warn
[test] <1> Error
[test] <1> Error
```

@kbenne My money is on the use of a shared lib for ruby: https://github.com/NREL/OpenStudio/commit/50f6c0378ce1e5e4e48c525d7d1e2e80fd6adcb2 . Thoughts?

I think we basically have one logger in the libopenstudiolib and ANOTHER one in the ruby shared lib.
The Ruby shared lib one is correctly set to log level = Error, but the other one is still set to the default of Warn. https://github.com/NREL/OpenStudio/blob/33626993b6315a614eb67de7b158bf4e5fb384d9/src/utilities/core/Logger.cpp#L54

Both are triggered... which explains why issuing an Info shows 0 message, Warn shows 1 message, and anything Error or above shows 2 messages.
On windows, if here I modify this line: https://github.com/NREL/OpenStudio/blob/33626993b6315a614eb67de7b158bf4e5fb384d9/src/cli/main.cpp#L299

to be

```diff
+   // Disable this logger, we have a duplicate in the ruby shared lib
+   openstudio::Logger::instance().standardOutLogger().disable();
+   openstudio::Logger::instance().standardErrLogger().disable();
    result = openstudio::rubyCLI(rubyEngine);
```

Then past the initial extra messages issued when we locate the DLL via getOpenStudioModule, I get only one set of messages

```shell
$ Products\openstudio.exe execute_ruby_script .\resources\Examples\compact_osw\test.rb
[2023-04-04 13:14:08.970602] [0x00003a00] [info]    getOpenStudioModule, szPath = 'C:\src\OpenStudio\build-debug\Products\openstudiolib.dll'
[2023-04-04 13:14:08.973631] [0x00003a00] [info]    completeAndNormalize: looking for p = C:/src/OpenStudio/build-debug/Products/openstudiolib.dll
[2023-04-04 13:14:08.974637] [0x00003a00] [info]    completeAndNormalize: temp = C:/src/OpenStudio/build-debug/Products/openstudiolib.dll
[2023-04-04 13:14:08.981796] [0x00003a00] [info]    completeAndNormalize: result = C:/src/OpenStudio/build-debug/Products/openstudiolib.dll
[2023-04-04 13:14:08.982718] [0x00003a00] [info]    getOpenStudioModule, found 'C:/src/OpenStudio/build-debug/Products/openstudiolib.dll'

W, [2023-04-04T13:14:13.052013 #6864]  WARN -- : STDOUT Warn
E, [2023-04-04T13:14:13.052447 #6864] ERROR -- : STDOUT Error
[test] <1> Error
```
@jmarrec You had fixed this issue, but it's no longer working for Windows w/ OS 3.7. You can easily reproduce the issue by cloning OpenStudio-HPXML and running `openstudio tasks.rb update_hpxmls`. We are using `OpenStudio::Logger.instance.standardOutLogger.setLogLevel(OpenStudio::Fatal)` to try to suppress messages.

Linux w/ C++ CLI:
![image](https://github.com/NREL/OpenStudio/assets/5861765/51662530-0150-4bb0-a81e-43f6cdb70029)

Windows w/ C++ CLI:
![image](https://github.com/NREL/OpenStudio/assets/5861765/39f4609f-5af9-4137-9126-f5a41800162c)

I was doing some more testing and realized this broke w/ the switch to the C++ CLI. It's easy to confirm because you can use the 3.7.0 release of OpenStudio and run the above command using `classic` or not. When running with classic the standardOutLogger's log level is correctly used (see below); when running w/o classic, it is not (see above).

Windows w/ classic CLI:
![image](https://github.com/NREL/OpenStudio/assets/5861765/6aea2660-b06d-4ea6-8355-6595f28a818f)

@shorowit I guess it was misleading having closed this issue. What I did was a mere workaround for the "classic" version in https://github.com/NREL/OpenStudio/pull/4852/commits/83015445b16e5032289600c2b723d4d0ce0f415a .
We've had a failing test since for the labs version.

The issue is that the Logger is a Static Singleton. And We get one in the libopenstudiolib, but we also get one in the rubyengine and one in the pythonengine (which are `dlopen`ed). So we end up with several logger instances... I don't know how to resolve that situation yet, and I reckon we'll need @kbenne 's magic touch for this.

Fix patch:
diff --git a/ProjectMacros.cmake b/ProjectMacros.cmake
index 9a06bdec2c8..b02c5f25969 100644
--- a/ProjectMacros.cmake
+++ b/ProjectMacros.cmake
@@ -281,6 +281,7 @@ macro(MAKE_SWIG_TARGET NAME SIMPLENAME KEY_I_FILE I_FILES PARENT_TARGET PARENT_S
     set_target_properties(${swig_target} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ruby/")
     target_link_libraries(${swig_target} ${${PARENT_TARGET}_depends})
     target_include_directories(${swig_target} SYSTEM PRIVATE ${RUBY_INCLUDE_DIRS})
+    target_compile_definitions(${swig_target} PRIVATE SHARED_OS_LIBS)
     add_dependencies(${swig_target} ${PARENT_TARGET})
 
     execute_process(COMMAND \"${CMAKE_COMMAND}\" -E copy \"\${resolved_item_var}\" \"\${CMAKE_INSTALL_PREFIX}/Ruby/openstudio/\")
@@ -355,6 +356,7 @@ macro(MAKE_SWIG_TARGET NAME SIMPLENAME KEY_I_FILE I_FILES PARENT_TARGET PARENT_S
       ${SWIG_WRAPPER}
     )
 
+    target_compile_definitions(${swig_target} PRIVATE SHARED_OS_LIBS)
     set_target_properties(${swig_target} PROPERTIES OUTPUT_NAME _${LOWER_NAME})
     set_target_properties(${swig_target} PROPERTIES PREFIX "")
     set_target_properties(${swig_target} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/python/")
diff --git a/python/engine/CMakeLists.txt b/python/engine/CMakeLists.txt
index 1343becdea3..346a0e62a08 100644
--- a/python/engine/CMakeLists.txt
+++ b/python/engine/CMakeLists.txt
@@ -30,8 +30,8 @@ target_include_directories(pythonengine
 target_link_libraries(
   pythonengine
   PRIVATE
+  openstudiolib
   openstudio_scriptengine
-  openstudio_utilities_minimal
   CONAN_PKG::fmt
   Python::Python
 )
diff --git a/src/airflow/Airflow.i b/src/airflow/Airflow.i
index ddbe67984e2..c2147d18c92 100644
--- a/src/airflow/Airflow.i
+++ b/src/airflow/Airflow.i
@@ -5,7 +5,7 @@
 %module openstudioairflow
 #endif
 
-#define UTILITIES_API
+%include <utilities/UtilitiesAPI.hpp>
 #define AIRFLOW_API
 
 %include <utilities/core/CommonInclude.i>
diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt
index b4777f3b707..6a0315f1247 100644
--- a/src/cli/CMakeLists.txt
+++ b/src/cli/CMakeLists.txt
@@ -82,6 +82,15 @@ endif()
 ###############################################################################
 
 if(BUILD_TESTING)
+  # openstudio_tests is a test target for C++ based tests that extend across multiple
+  # compiled units such as the case for openstudio cli. 
+  # For example, here is a good place to test issues that are concerned with
+  # the complexities of sharing data between the Python and Ruby script engines.
+  set(openstudio_tests_src
+    "test/Logger_GTest.cpp"
+  )
+  CREATE_TEST_TARGETS(openstudio "${openstudio_tests_src}" openstudiolib openstudio_workflow)
+  add_dependencies(openstudio_tests openstudio)
 
   # Just for convenience: having to VT the seb model which is at 1.11.5 can be very time consuming, especially on debug builds
   add_custom_target(update_seb_osm_in_build_dir
diff --git a/src/cli/main.cpp b/src/cli/main.cpp
index a4453fc6301..0763eaf614a 100644
--- a/src/cli/main.cpp
+++ b/src/cli/main.cpp
@@ -80,14 +80,6 @@ int main(int argc, char* argv[]) {
                "└{0:─^{2}}┘",
                "", "The `classic` command is deprecated and will be removed in a future release", 80);
     fmt::print("\n");
-#if defined _WIN32
-    // Poor man's hack #4847
-    // Disable this logger, we have a duplicate in the ruby shared lib
-    openstudio::Logger::instance().standardOutLogger().disable();
-    openstudio::Logger::instance().standardErrLogger().disable();
-    // Avoid getting some messages during getOpenStudioModule() when we locate the DLL
-    openstudio::StringStreamLogSink sink;
-#endif
     result = openstudio::rubyCLI(rubyEngine);
   } else {
     CLI::App app{"openstudio"};
diff --git a/src/energyplus/EnergyPlus.i b/src/energyplus/EnergyPlus.i
index fdafa8f52da..fd7b3c7c80d 100644
--- a/src/energyplus/EnergyPlus.i
+++ b/src/energyplus/EnergyPlus.i
@@ -6,7 +6,7 @@
 #endif
 
 
-#define UTILITIES_API
+%include <utilities/UtilitiesAPI.hpp>
 #define MODEL_API
 #define ENERGYPLUS_API
 
diff --git a/src/epjson/epJSON.i b/src/epjson/epJSON.i
index 3a142272df4..e90de710cdc 100644
--- a/src/epjson/epJSON.i
+++ b/src/epjson/epJSON.i
@@ -6,7 +6,7 @@
 #endif
 
 
-#define UTILITIES_API
+%include <utilities/UtilitiesAPI.hpp>
 #define EPJSON_API
 
 // You're better off just loading the json directly in the target language, so ignore
diff --git a/src/gbxml/gbXML.i b/src/gbxml/gbXML.i
index bafe91eccd1..42523eadb38 100644
--- a/src/gbxml/gbXML.i
+++ b/src/gbxml/gbXML.i
@@ -6,7 +6,7 @@
 #endif
 
 
-#define UTILITIES_API
+%include <utilities/UtilitiesAPI.hpp>
 #define GBXML_API
 
 %include <utilities/core/CommonInclude.i>
diff --git a/src/gltf/Gltf.i b/src/gltf/Gltf.i
index fddf3ddf86a..a4b8d69afcd 100644
--- a/src/gltf/Gltf.i
+++ b/src/gltf/Gltf.i
@@ -5,7 +5,7 @@
 %module openstudiogltf
 #endif
 
-#define UTILITIES_API
+%include <utilities/UtilitiesAPI.hpp>
 #define MODEL_API
 #define GLTF_API
 
diff --git a/src/isomodel/ISOModel.i b/src/isomodel/ISOModel.i
index a55f8206f29..95451791acc 100644
--- a/src/isomodel/ISOModel.i
+++ b/src/isomodel/ISOModel.i
@@ -5,7 +5,7 @@
 %module openstudioisomodel
 #endif
 
-#define UTILITIES_API
+%include <utilities/UtilitiesAPI.hpp>
 #define ISOMODEL_API
 
 %include <utilities/core/CommonInclude.i>
diff --git a/src/measure/Measure.i b/src/measure/Measure.i
index a3cedd922cd..604b70f1381 100644
--- a/src/measure/Measure.i
+++ b/src/measure/Measure.i
@@ -6,7 +6,7 @@
 #endif
 
 
-#define UTILITIES_API
+%include <utilities/UtilitiesAPI.hpp>
 #define MODEL_API
 #define STANDARDSINTERFACE_API
 #define MEASURE_API
diff --git a/src/model/ModelGeometry.i b/src/model/ModelGeometry.i
index b2113b882dc..94bb4a01921 100644
--- a/src/model/ModelGeometry.i
+++ b/src/model/ModelGeometry.i
@@ -6,6 +6,7 @@
 #endif
 
 
+%include <utilities/UtilitiesAPI.hpp>
 %include <model/Model_Common_Include.i>
 %import <model/ModelCore.i>
 %import <model/ModelSimulation.i>
diff --git a/src/model/Model_Common_Include.i b/src/model/Model_Common_Include.i
index da4d4b71f73..783ce4cb130 100644
--- a/src/model/Model_Common_Include.i
+++ b/src/model/Model_Common_Include.i
@@ -1,7 +1,7 @@
 #ifndef MODEL_COMMON_INCLUDE_I
 #define MODEL_COMMON_INCLUDE_I
 
-#define UTILITIES_API
+%include <utilities/UtilitiesAPI.hpp>
 #define MODEL_API
 
 %include <utilities/core/CommonInclude.i>
diff --git a/src/osversion/OSVersion.i b/src/osversion/OSVersion.i
index 79757ea46b3..d8889b91404 100644
--- a/src/osversion/OSVersion.i
+++ b/src/osversion/OSVersion.i
@@ -6,7 +6,7 @@
 #endif
 
 
-#define UTILITIES_API
+%include <utilities/UtilitiesAPI.hpp>
 #define MODEL_API
 #define OSVERSION_API
 
diff --git a/src/radiance/AnnualIlluminanceMap.i b/src/radiance/AnnualIlluminanceMap.i
index 7f2b70a2c53..17e0d62fe28 100644
--- a/src/radiance/AnnualIlluminanceMap.i
+++ b/src/radiance/AnnualIlluminanceMap.i
@@ -1,7 +1,7 @@
 #ifndef RADIANCE_ANNUALILLUMINANCEMAP_I
 #define RADIANCE_ANNUALILLUMINANCEMAP_I
 
-#define UTILITIES_API
+%include <utilities/UtilitiesAPI.hpp>
 #define RADIANCE_API
 
 %include <utilities/core/CommonInclude.i>
diff --git a/src/radiance/HeaderInfo.i b/src/radiance/HeaderInfo.i
index d16280700b7..399c38753d8 100644
--- a/src/radiance/HeaderInfo.i
+++ b/src/radiance/HeaderInfo.i
@@ -1,7 +1,7 @@
 #ifndef RADIANCE_HEADERINFO_I
 #define RADIANCE_HEADERINFO_I
 
-#define UTILITIES_API
+%include <utilities/UtilitiesAPI.hpp>
 #define RADIANCE_API
 
 %include <utilities/core/CommonInclude.i>
diff --git a/src/radiance/Radiance.i b/src/radiance/Radiance.i
index c5307866fe8..860defa3fb4 100644
--- a/src/radiance/Radiance.i
+++ b/src/radiance/Radiance.i
@@ -6,7 +6,7 @@
 #endif
 
 
-#define UTILITIES_API
+%include <utilities/UtilitiesAPI.hpp>
 #define RADIANCE_API
 
 %include <utilities/core/CommonInclude.i>
diff --git a/src/sdd/SDD.i b/src/sdd/SDD.i
index 4eb762bc14e..e61ea4a0122 100644
--- a/src/sdd/SDD.i
+++ b/src/sdd/SDD.i
@@ -6,7 +6,7 @@
 #endif
 
 
-#define UTILITIES_API
+%include <utilities/UtilitiesAPI.hpp>
 #define SDD_API
 
 %include <utilities/core/CommonInclude.i>
diff --git a/src/utilities/Utilities.i b/src/utilities/Utilities.i
index 048a129feba..a43f8906e38 100644
--- a/src/utilities/Utilities.i
+++ b/src/utilities/Utilities.i
@@ -6,7 +6,7 @@
 #endif
 
 
-#define UTILITIES_API
+%include <utilities/UtilitiesAPI.hpp>
 #define UTILITIES_TEMPLATE_EXT
 
 %include <utilities/core/CommonInclude.i>
diff --git a/src/utilities/UtilitiesBCL.i b/src/utilities/UtilitiesBCL.i
index c88f9f4afbb..eaec52d9742 100644
--- a/src/utilities/UtilitiesBCL.i
+++ b/src/utilities/UtilitiesBCL.i
@@ -7,8 +7,7 @@
 #endif
 
 
-#define UTILITIES_API
-#define UTILITIES_TEMPLATE_EXT
+%include <utilities/UtilitiesAPI.hpp>
 
 %include <utilities/core/CommonInclude.i>
 %import <utilities/core/CommonImport.i>
diff --git a/src/utilities/UtilitiesCore.i b/src/utilities/UtilitiesCore.i
index 1317a820269..894c99670c0 100644
--- a/src/utilities/UtilitiesCore.i
+++ b/src/utilities/UtilitiesCore.i
@@ -5,9 +5,7 @@
 %module openstudioutilitiescore
 #endif
 
-
-#define UTILITIES_API
-#define UTILITIES_TEMPLATE_EXT
+%include <utilities/UtilitiesAPI.hpp>
 
 %include <utilities/core/CommonInclude.i>
 %include <utilities/core/CommonImport.i> // CommonImport is included in this module
diff --git a/src/utilities/UtilitiesData.i b/src/utilities/UtilitiesData.i
index e94231ea114..9e64e1740d3 100644
--- a/src/utilities/UtilitiesData.i
+++ b/src/utilities/UtilitiesData.i
@@ -6,9 +6,7 @@
 #endif
 
 
-#define UTILITIES_API
-#define UTILITIES_TEMPLATE_EXT
-
+%include <utilities/UtilitiesAPI.hpp>
 %include <utilities/core/CommonInclude.i>
 %import <utilities/core/CommonImport.i>
 %import <utilities/UtilitiesTime.i>
diff --git a/src/utilities/UtilitiesFileTypes.i b/src/utilities/UtilitiesFileTypes.i
index 9e48bae453e..a5643b46e13 100644
--- a/src/utilities/UtilitiesFileTypes.i
+++ b/src/utilities/UtilitiesFileTypes.i
@@ -5,9 +5,7 @@
 %module openstudioutilitiesfiletypes
 #endif
 
-#define UTILITIES_API
-#define UTILITIES_TEMPLATE_EXT
-
+%include <utilities/UtilitiesAPI.hpp>
 %include <utilities/core/CommonInclude.i>
 %import <utilities/core/CommonImport.i>
 %import <utilities/UtilitiesCore.i>
diff --git a/src/utilities/UtilitiesGeometry.i b/src/utilities/UtilitiesGeometry.i
index 2f858b12b6b..51256a926d0 100644
--- a/src/utilities/UtilitiesGeometry.i
+++ b/src/utilities/UtilitiesGeometry.i
@@ -6,9 +6,7 @@
 #endif
 
 
-#define UTILITIES_API
-#define UTILITIES_TEMPLATE_EXT
-
+%include <utilities/UtilitiesAPI.hpp>
 %include <utilities/core/CommonInclude.i>
 %import <utilities/core/CommonImport.i>
 %import <utilities/UtilitiesPlot.i>
diff --git a/src/utilities/UtilitiesIdd.i b/src/utilities/UtilitiesIdd.i
index a138515bc30..025afc2a4ea 100644
--- a/src/utilities/UtilitiesIdd.i
+++ b/src/utilities/UtilitiesIdd.i
@@ -6,9 +6,7 @@
 #endif
 
 
-#define UTILITIES_API
-#define UTILITIES_TEMPLATE_EXT
-
+%include <utilities/UtilitiesAPI.hpp>
 %include <utilities/core/CommonInclude.i>
 %import <utilities/core/CommonImport.i>
 %import <utilities/UtilitiesCore.i>
diff --git a/src/utilities/UtilitiesIdf.i b/src/utilities/UtilitiesIdf.i
index 0a593175449..b7a6ac8c574 100644
--- a/src/utilities/UtilitiesIdf.i
+++ b/src/utilities/UtilitiesIdf.i
@@ -6,9 +6,7 @@
 #endif
 
 
-#define UTILITIES_API
-#define UTILITIES_TEMPLATE_EXT
-
+%include <utilities/UtilitiesAPI.hpp>
 %include <utilities/core/CommonInclude.i>
 %import <utilities/core/CommonImport.i>
 %import <utilities/UtilitiesCore.i>
diff --git a/src/utilities/UtilitiesPlot.i b/src/utilities/UtilitiesPlot.i
index b897c4c6d66..3395c8fc00a 100644
--- a/src/utilities/UtilitiesPlot.i
+++ b/src/utilities/UtilitiesPlot.i
@@ -6,9 +6,7 @@
 #endif
 
 
-#define UTILITIES_API
-#define UTILITIES_TEMPLATE_EXT
-
+%include <utilities/UtilitiesAPI.hpp>
 %include <utilities/core/CommonInclude.i>
 %import <utilities/core/CommonImport.i>
 %import <utilities/UtilitiesData.i>
diff --git a/src/utilities/UtilitiesSql.i b/src/utilities/UtilitiesSql.i
index 5509fb2fb45..948e467c9d4 100644
--- a/src/utilities/UtilitiesSql.i
+++ b/src/utilities/UtilitiesSql.i
@@ -6,9 +6,7 @@
 #endif
 
 
-#define UTILITIES_API
-#define UTILITIES_TEMPLATE_EXT
-
+%include <utilities/UtilitiesAPI.hpp>
 %include <utilities/core/CommonInclude.i>
 %import <utilities/core/CommonImport.i>
 %import <utilities/UtilitiesGeometry.i>
diff --git a/src/utilities/UtilitiesTime.i b/src/utilities/UtilitiesTime.i
index 71e7c237bc1..6ebea993931 100644
--- a/src/utilities/UtilitiesTime.i
+++ b/src/utilities/UtilitiesTime.i
@@ -6,9 +6,7 @@
 #endif
 
 
-#define UTILITIES_API
-#define UTILITIES_TEMPLATE_EXT
-
+%include <utilities/UtilitiesAPI.hpp>
 %include <utilities/core/CommonInclude.i>
 %import <utilities/core/CommonImport.i>
 %import <utilities/UtilitiesCore.i>
diff --git a/src/utilities/UtilitiesUnits.i b/src/utilities/UtilitiesUnits.i
index 2a87b1eabcb..744bbefe8f9 100644
--- a/src/utilities/UtilitiesUnits.i
+++ b/src/utilities/UtilitiesUnits.i
@@ -6,9 +6,7 @@
 #endif
 
 
-#define UTILITIES_API
-#define UTILITIES_TEMPLATE_EXT
-
+%include <utilities/UtilitiesAPI.hpp>
 %include <utilities/core/CommonInclude.i>
 %import <utilities/core/CommonImport.i>
 %import <utilities/UtilitiesCore.i>
diff --git a/src/utilities/UtilitiesXML.i b/src/utilities/UtilitiesXML.i
index af242d86922..21566eb9ca0 100644
--- a/src/utilities/UtilitiesXML.i
+++ b/src/utilities/UtilitiesXML.i
@@ -6,9 +6,7 @@
 #endif
 
 
-#define UTILITIES_API
-#define UTILITIES_TEMPLATE_EXT
-
+%include <utilities/UtilitiesAPI.hpp>
 %include <utilities/core/CommonInclude.i>
 %import <utilities/core/CommonImport.i>
 %import <utilities/UtilitiesCore.i>
diff --git a/src/utilities/core/LogSink.hpp b/src/utilities/core/LogSink.hpp
index e871fe6e072..df737967bf8 100644
--- a/src/utilities/core/LogSink.hpp
+++ b/src/utilities/core/LogSink.hpp
@@ -18,9 +18,11 @@
 
 namespace openstudio {
 
+class Logger;
+
 namespace detail {
   class LogSink_Impl;
-}
+}  // namespace detail
 
 /// LogSink is a class for managing sinks for log messages, e.g. files, streams, etc.
 class UTILITIES_API LogSink
@@ -76,7 +78,7 @@ class UTILITIES_API LogSink
   void setFormatter(const boost::log::formatter& fmter);
 
  protected:
-  friend class LoggerSingleton;
+  friend class Logger;
 
   // does not register in the global logger
   LogSink();
diff --git a/src/utilities/core/Logger.cpp b/src/utilities/core/Logger.cpp
index 4967ebfb934..020f70b4135 100644
--- a/src/utilities/core/Logger.cpp
+++ b/src/utilities/core/Logger.cpp
@@ -11,8 +11,7 @@
 
 #include <boost/core/null_deleter.hpp>
 
-namespace sinks = boost::log::sinks;
-namespace keywords = boost::log::keywords;
+#include <utility>
 
 namespace openstudio {
 
@@ -21,7 +20,13 @@ void logFree(LogLevel level, const std::string& channel, const std::string& mess
   BOOST_LOG_SEV(openstudio::Logger::instance().loggerFromChannel(channel), level) << message;
 }
 
-LoggerSingleton::LoggerSingleton() {
+// Meyers' singleton
+Logger& Logger::instance() {
+  static Logger instance;
+  return instance;
+}
+
+Logger::Logger() {
   // Make current thread id attribute available to logging
   boost::log::core::get()->add_global_attribute("ThreadId", boost::log::attributes::make_function(&std::this_thread::get_id));
 
@@ -38,47 +43,39 @@ LoggerSingleton::LoggerSingleton() {
   //this->addSink(m_standardErrLogger.sink());
 }
 
-LoggerSingleton::~LoggerSingleton() {
-  // unregister Qt message handler
-  //qInstallMsgHandler(consoleLogQtMessage);
-}
-
-LogSink LoggerSingleton::standardOutLogger() const {
+LogSink Logger::standardOutLogger() const {
   std::shared_lock l{m_mutex};
 
   return m_standardOutLogger;
 }
 
-LogSink LoggerSingleton::standardErrLogger() const {
+LogSink Logger::standardErrLogger() const {
   std::shared_lock l{m_mutex};
 
   return m_standardErrLogger;
 }
 
-LoggerType& LoggerSingleton::loggerFromChannel(const LogChannel& logChannel) {
+LoggerType& Logger::loggerFromChannel(const LogChannel& logChannel) {
   std::shared_lock l{m_mutex};
 
   auto it = m_loggerMap.find(logChannel);
   if (it == m_loggerMap.end()) {
-    //LoggerType newLogger(keywords::channel = logChannel, keywords::severity = Debug);
-    LoggerType newLogger(keywords::channel = logChannel);
-
-    std::pair<LogChannel, LoggerType> newPair(logChannel, newLogger);
-
     // Drop the read lock and grab a write lock - we need to add the new file to the map
     // this will reduce contention when multiple threads trying to log at once.
     l.unlock();
     std::unique_lock l2{m_mutex};
 
-    std::pair<LoggerMapType::iterator, bool> inserted = m_loggerMap.insert(newPair);
+    //LoggerType newLogger(boost::log::keywords::channel = logChannel, boost::log::keywords::severity = Debug);
+    // No need for try_emplace, this we checked it wasn't in the map
+    auto [it2, inserted] = m_loggerMap.emplace(logChannel, LoggerType(boost::log::keywords::channel = logChannel));
 
-    return inserted.first->second;
+    return it2->second;
   }
 
   return it->second;
 }
 
-bool LoggerSingleton::findSink(boost::shared_ptr<LogSinkBackend> sink) {
+bool Logger::findSink(boost::shared_ptr<LogSinkBackend> sink) {
   std::unique_lock l{m_mutex};
 
   auto it = m_sinks.find(sink);
@@ -86,7 +83,7 @@ bool LoggerSingleton::findSink(boost::shared_ptr<LogSinkBackend> sink) {
   return (it != m_sinks.end());
 }
 
-void LoggerSingleton::addSink(boost::shared_ptr<LogSinkBackend> sink) {
+void Logger::addSink(boost::shared_ptr<LogSinkBackend> sink) {
   std::shared_lock l{m_mutex};
 
   auto it = m_sinks.find(sink);
@@ -104,7 +101,7 @@ void LoggerSingleton::addSink(boost::shared_ptr<LogSinkBackend> sink) {
   }
 }
 
-void LoggerSingleton::removeSink(boost::shared_ptr<LogSinkBackend> sink) {
+void Logger::removeSink(boost::shared_ptr<LogSinkBackend> sink) {
   std::shared_lock l{m_mutex};
 
   auto it = m_sinks.find(sink);
@@ -122,7 +119,7 @@ void LoggerSingleton::removeSink(boost::shared_ptr<LogSinkBackend> sink) {
   }
 }
 
-void LoggerSingleton::addTimeStampToLogger() {
+void Logger::addTimeStampToLogger() {
   std::unique_lock l{m_mutex};
 
   // Add a TimeStamp attribute, same as boost::log::add_common_attributes() would do
diff --git a/src/utilities/core/Logger.hpp b/src/utilities/core/Logger.hpp
index dceab417755..6c848a472fa 100644
--- a/src/utilities/core/Logger.hpp
+++ b/src/utilities/core/Logger.hpp
@@ -8,17 +8,18 @@
 
 #include "../UtilitiesAPI.hpp"
 
-#include "Singleton.hpp"
-#include "Exception.hpp"
 #include "Compare.hpp"
+#include "LogMessage.hpp"
 #include "LogSink.hpp"
+#include "Exception.hpp"
 
 #include <boost/shared_ptr.hpp>
 
-#include <sstream>
-#include <set>
 #include <map>
+#include <memory>
+#include <set>
 #include <shared_mutex>
+#include <sstream>
 
 /// defines method logChannel() to get a logger for a class
 #define REGISTER_LOGGER(__logChannel__)        \
@@ -52,21 +53,23 @@
 namespace openstudio {
 
 class OSWorkflow;
+class LogSink;
+namespace detail {
+  class LogSink_Impl;
+}  // namespace detail
 
 /// convenience function for SWIG, prefer macros in C++
 UTILITIES_API void logFree(LogLevel level, const std::string& channel, const std::string& message);
 
-/** Singleton logger class.  Singleton Logger object maintains logging state throughout
-   *   program execution.
-   */
-class UTILITIES_API LoggerSingleton
+class UTILITIES_API Logger
 {
-
-  friend class Singleton<LoggerSingleton>;
-
  public:
-  /// destructor, cleans up, writes xml file footers, etc
-  ~LoggerSingleton();
+  static Logger& instance();
+
+  Logger(const Logger& other) = delete;
+  Logger(Logger&& other) = delete;
+  Logger& operator=(const Logger&) = delete;
+  Logger& operator=(Logger&&) = delete;
 
   /// get logger for standard out
   LogSink standardOutLogger() const;
@@ -95,8 +98,8 @@ class UTILITIES_API LoggerSingleton
   void addTimeStampToLogger();
 
  private:
-  /// private constructor
-  LoggerSingleton();
+  Logger();
+  ~Logger() = default;
 
   mutable std::shared_mutex m_mutex;
 
@@ -115,15 +118,6 @@ class UTILITIES_API LoggerSingleton
   SinkSetType m_sinks;
 };
 
-#if _WIN32 || _MSC_VER
-
-/// Explicitly instantiate and export LoggerSingleton Singleton template instance
-/// so that the same instance is shared between the DLL's that link to Utilities.dll
-UTILITIES_TEMPLATE_EXT template class UTILITIES_API openstudio::Singleton<LoggerSingleton>;
-
-#endif
-
-using Logger = openstudio::Singleton<LoggerSingleton>;
 }  // namespace openstudio
 
 #endif  // UTILITIES_CORE_LOGGER_HPP
diff --git a/src/utilities/core/Logger.i b/src/utilities/core/Logger.i
index 4178110f9f7..cb0961b79ac 100644
--- a/src/utilities/core/Logger.i
+++ b/src/utilities/core/Logger.i
@@ -22,14 +22,12 @@
 
 %ignore std::vector<openstudio::LogMessage>::vector(size_type);
 %ignore std::vector<openstudio::LogMessage>::resize(size_type);
-%ignore openstudio::LoggerSingleton::loggerFromChannel;
+%ignore openstudio::Logger::loggerFromChannel;
 %ignore openstudio::LogSink::setFormatter;
 
 %template(LogMessageVector) std::vector<openstudio::LogMessage>;
 %template(OptionalLogMessage) boost::optional<openstudio::LogMessage>;
 
-%template(Logger) openstudio::Singleton<openstudio::LoggerSingleton>;
-
 // Ignore std::thread::id stuff
 %ignore openstudio::LogSink::threadId;
 %ignore openstudio::LogSink::setThreadId;


Test patch:
diff --git a/src/cli/test/Logger_GTest.cpp b/src/cli/test/Logger_GTest.cpp
new file mode 100644
index 00000000000..3fa6a48449e
--- /dev/null
+++ b/src/cli/test/Logger_GTest.cpp
@@ -0,0 +1,24 @@
+#include "../../scriptengine/ScriptEngine.hpp"
+#include "../../utilities/core/Logger.hpp"
+
+#include <gtest/gtest.h>
+
+// cppcheck-suppress syntaxError
+TEST(OpenStudioTest, LoggerGlobal) {
+  auto isEnabled = openstudio::Logger::instance().standardOutLogger().isEnabled();
+  EXPECT_TRUE(isEnabled);
+
+  openstudio::ScriptEngineInstance pythonEngine("pythonengine", {});
+  openstudio::ScriptEngineInstance rubyEngine("rubyengine", {});
+
+  pythonEngine->exec("openstudio.Logger.instance().standardOutLogger().disable()");
+  isEnabled = openstudio::Logger::instance().standardOutLogger().isEnabled();
+  EXPECT_FALSE(isEnabled);
+
+  rubyEngine->exec("OpenStudio::Logger::instance().standardOutLogger().enable()");
+  isEnabled = openstudio::Logger::instance().standardOutLogger().isEnabled();
+  EXPECT_TRUE(isEnabled);
+
+  pythonEngine.reset();
+  rubyEngine.reset();
+}