├── icon.png ├── src └── SRTM │ ├── ISRTMDataCell.cs │ ├── Sources │ ├── FuncSource.cs │ ├── SourceHelpers.cs │ ├── NASA │ │ └── NASASource.cs │ └── USGS │ │ └── USGSSource.cs │ ├── SRTM.csproj │ ├── EmptySRTMDataCell.cs │ ├── ISRTMSource.cs │ ├── ISRTMData.cs │ ├── SRTMDataCell.cs │ ├── SRTMData.cs │ └── Logging │ └── LibLog.cs ├── test └── SRTM.Tests.Functional │ ├── SRTM.Tests.Functional.csproj │ └── Program.cs ├── .gitignore ├── .github └── workflows │ ├── build.yml │ └── release.yml ├── LICENSE.md ├── SRTM.sln └── README.md /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itinero/srtm/HEAD/icon.png -------------------------------------------------------------------------------- /src/SRTM/ISRTMDataCell.cs: -------------------------------------------------------------------------------- 1 | namespace SRTM 2 | { 3 | public interface ISRTMDataCell 4 | { 5 | int Latitude { get; } 6 | 7 | int Longitude { get; } 8 | 9 | int? GetElevation(double latitude, double longitude); 10 | 11 | double? GetElevationBilinear(double latitude, double longitude); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/SRTM.Tests.Functional/SRTM.Tests.Functional.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net5.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/SRTM/Sources/FuncSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SRTM.Sources 4 | { 5 | internal sealed class FuncSource : ISRTMSource 6 | { 7 | private readonly Func<(string path, string name), bool> _getMissingCell; 8 | 9 | public FuncSource(Func<(string path, string name), bool> getMissingCell) 10 | { 11 | _getMissingCell = getMissingCell; 12 | } 13 | 14 | public bool GetMissingCell(string path, string name) 15 | { 16 | return _getMissingCell((path, name)); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | [Oo]bj 2 | [Bb]in 3 | *.user 4 | *.suo 5 | *.[Cc]ache 6 | *.bak 7 | *.ncb 8 | *.log 9 | *.DS_Store 10 | [Tt]humbs.db 11 | _ReSharper.* 12 | *.resharper 13 | Ankh.NoLoad 14 | Core/Output*/ 15 | UI/Output*/ 16 | UI/WinForms/Output*/ 17 | Data/Oracle/Output*/ 18 | Data/PostgreSQL/Output*/ 19 | Data/Redis/Output*/ 20 | Data/SQLServer/Output*/ 21 | Data/SQLite/Output*/ 22 | Core/OsmSharp.UnitTests/test-results*/ 23 | *.userprefs 24 | Output*/ 25 | OutputAndroid*/ 26 | OutputWindowsPhone*/ 27 | *.psess 28 | test-results*/ 29 | *.vsp 30 | .DotSettings 31 | *.vspx 32 | *.patch 33 | TestResults*/ 34 | 35 | #Intellij cache 36 | .idea/ 37 | 38 | packages/* 39 | !packages/repositories.config 40 | .vs/* 41 | 42 | TestResult*.xml 43 | *.routerdb 44 | *.osm.pbf 45 | 46 | *.shp 47 | *.dbf 48 | *.shx 49 | *.shx 50 | *.hgt.zip 51 | srtm-cache*/ 52 | 53 | 54 | islands_*.geojson -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: [ develop ] 6 | 7 | jobs: 8 | build-and-test: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | with: 15 | submodules: 'true' 16 | - name: Setup .NET Core 5. 17 | uses: actions/setup-dotnet@v1 18 | with: 19 | dotnet-version: 5.0.101 20 | - name: Restore packages. 21 | run: dotnet restore 22 | - name: Build all projects. 23 | run: dotnet build --configuration Release --no-restore 24 | - name: Nuget Pack 25 | run: dotnet pack -c release 26 | working-directory: ./src/SRTM/ 27 | - name: Nuget push 28 | run: dotnet nuget push **/*.nupkg --skip-duplicate -k ${{ secrets.GITHUB_TOKEN }} -s https://nuget.pkg.github.com/itiner/index.json 29 | working-directory: ./src/ -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release to nuget 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | 7 | jobs: 8 | build-and-test: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | with: 15 | submodules: 'true' 16 | - name: Setup .NET Core 5. 17 | uses: actions/setup-dotnet@v1 18 | with: 19 | dotnet-version: 5.0.101 20 | - name: Restore packages. 21 | run: dotnet restore 22 | - name: Build all projects. 23 | run: dotnet build --configuration Release --no-restore 24 | - name: Nuget Pack 25 | run: dotnet pack -c release 26 | working-directory: ./src/SRTM/ 27 | - name: Nuget push 28 | run: dotnet nuget push **/*.nupkg --skip-duplicate -k ${{ secrets.NUGET_TOKEN }} -s https://api.nuget.org/v3/index.json 29 | working-directory: ./src/SRTM/ -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Ben Abelshausen, Alpine Chough Software 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/SRTM/SRTM.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Library 5 | netstandard2.0 6 | 7 | 8 | latest 9 | 10 | 11 | 12 | LIBLOG_PORTABLE 13 | 0.0.7 14 | Ben Abelshausen 15 | Itinero 16 | A library to read SRTM data, loads missing files on-the-fly. 17 | Ben Abelshausen 18 | https://github.com/itinero/srtm/blob/master/LICENSE.md 19 | https://github.com/itinero/srtm 20 | https://github.com/itinero/srtm/raw/master/icon.png 21 | https://github.com/itinero/srtm 22 | Git 23 | srtm, gis 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/SRTM/EmptySRTMDataCell.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace SRTM 5 | { 6 | public class EmptySRTMDataCell : ISRTMDataCell 7 | { 8 | public EmptySRTMDataCell(string filepath) 9 | { 10 | if (!File.Exists(filepath)) 11 | throw new FileNotFoundException("File not found.", filepath); 12 | 13 | var filename = Path.GetFileName(filepath); 14 | filename = filename.Substring(0, filename.IndexOf('.')).ToLower(); // Path.GetFileNameWithoutExtension(filepath).ToLower(); 15 | var fileCoordinate = filename.Split(new[] { 'e', 'w' }); 16 | if (fileCoordinate.Length != 2) 17 | throw new ArgumentException("Invalid filename.", filepath); 18 | 19 | fileCoordinate[0] = fileCoordinate[0].TrimStart(new[] { 'n', 's' }); 20 | 21 | Latitude = int.Parse(fileCoordinate[0]); 22 | if (filename.Contains("s")) 23 | Latitude *= -1; 24 | 25 | Longitude = int.Parse(fileCoordinate[1]); 26 | if (filename.Contains("w")) 27 | Longitude *= -1; 28 | } 29 | 30 | public int Latitude { get; private set; } 31 | 32 | public int Longitude { get; private set; } 33 | 34 | public int? GetElevation(double latitude, double longitude) 35 | { 36 | return null; 37 | } 38 | 39 | public double? GetElevationBilinear(double latitude, double longitude) 40 | { 41 | return null; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/SRTM/ISRTMSource.cs: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | 3 | // Copyright (c) 2019, Tadas Juščius 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | namespace SRTM 24 | { 25 | /// 26 | /// SRTM source interface. 27 | /// 28 | public interface ISRTMSource 29 | { 30 | /// 31 | /// Gets SRTM data cell from source. 32 | /// 33 | /// Local system path where to save the data to. 34 | /// Name of the file to get. 35 | /// 36 | bool GetMissingCell(string path, string name); 37 | } 38 | } -------------------------------------------------------------------------------- /src/SRTM/Sources/SourceHelpers.cs: -------------------------------------------------------------------------------- 1 | using SRTM.Logging; 2 | using System; 3 | using System.IO; 4 | using System.Net; 5 | using System.Net.Http; 6 | 7 | namespace SRTM.Sources 8 | { 9 | public class SourceHelpers 10 | { 11 | /// 12 | /// Downloads a remote file and stores the data in the local one. 13 | /// 14 | public static bool Download(string local, string remote, bool logErrors = false) 15 | { 16 | var client = new HttpClient(); 17 | return PerformDownload(client, local, remote, logErrors); 18 | } 19 | 20 | /// 21 | /// Downloads a remote file and stores the data in the local one. The given credentials are used for authorization. 22 | /// 23 | public static bool DownloadWithCredentials(NetworkCredential credentials, string local, string remote, 24 | bool logErrors = false) 25 | { 26 | HttpClientHandler handler = new HttpClientHandler {Credentials = credentials}; 27 | var client = new HttpClient(handler); 28 | return PerformDownload(client, local, remote, logErrors); 29 | } 30 | 31 | private static bool PerformDownload(HttpClient client, string local, string remote, bool logErrors = false) 32 | { 33 | var Logger = LogProvider.For(); 34 | 35 | try 36 | { 37 | if (File.Exists(local)) 38 | { 39 | File.Delete(local); 40 | } 41 | 42 | using (var stream = client.GetStreamAsync(remote).Result) 43 | using (var outputStream = File.OpenWrite(local)) 44 | { 45 | stream.CopyTo(outputStream); 46 | } 47 | return true; 48 | } 49 | catch (Exception ex) 50 | { 51 | if (logErrors) 52 | { 53 | Logger.ErrorException("Download failed.", ex); 54 | } 55 | } 56 | return false; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/SRTM/Sources/NASA/NASASource.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using SRTM.Logging; 3 | 4 | // The MIT License (MIT) 5 | 6 | // Copyright (c) 2019, Tadas Juščius 7 | 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | namespace SRTM.Sources.NASA 27 | { 28 | /// 29 | /// Defines a NASA source of data. 30 | /// 31 | public class NASASource : ISRTMSource 32 | { 33 | private NetworkCredential _credentials; 34 | 35 | public NASASource(NetworkCredential credentials) 36 | { 37 | _credentials = credentials; 38 | } 39 | 40 | /// 41 | /// The source of the data. 42 | /// 43 | public const string SOURCE = @"https://e4ftl01.cr.usgs.gov/MEASURES/SRTMGL1.003/2000.02.11/"; 44 | 45 | /// 46 | /// Gets the missing cell. 47 | /// 48 | public bool GetMissingCell(string path, string name) 49 | { 50 | var filename = name + ".SRTMGL1.hgt.zip"; 51 | var local = System.IO.Path.Combine(path, name + ".hgt.zip"); 52 | 53 | var Logger = LogProvider.For(); 54 | Logger.Info("Downloading {0} ...", name); 55 | 56 | return SourceHelpers.DownloadWithCredentials(_credentials, local, SOURCE + filename); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/SRTM/ISRTMData.cs: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | 3 | // Copyright (c) 2017 Alpine Chough Software, Ben Abelshausen 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | namespace SRTM 24 | { 25 | /// 26 | /// SRTM data interface. 27 | /// 28 | public interface ISRTMData 29 | { 30 | /// 31 | /// Unloads all SRTM data cells. 32 | /// 33 | void Unload(); 34 | 35 | /// 36 | /// Gets the elevation. 37 | /// 38 | /// 39 | /// The height. Null, if elevation is not available. 40 | /// 41 | /// 42 | /// 43 | /// 44 | /// Represents errors that occur during application execution. 45 | /// 46 | int? GetElevation(double latitude, double longitude); 47 | 48 | /// 49 | /// Gets the elevation. Data is smoothed using bilinear interpolation. 50 | /// 51 | /// 52 | /// The height. Null, if elevation is not available. 53 | /// 54 | /// 55 | /// 56 | /// 57 | /// Represents errors that occur during application execution. 58 | /// 59 | double? GetElevationBilinear(double latitude, double longitude); 60 | } 61 | } -------------------------------------------------------------------------------- /src/SRTM/Sources/USGS/USGSSource.cs: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | 3 | // Copyright (c) 2017 Alpine Chough Software, Ben Abelshausen 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | using SRTM.Logging; 24 | 25 | namespace SRTM.Sources.USGS 26 | { 27 | /// 28 | /// Defines an USGS source of data. 29 | /// 30 | public class USGSSource : ISRTMSource 31 | { 32 | /// 33 | /// The source of the data. 34 | /// 35 | public const string SOURCE = @"https://dds.cr.usgs.gov/srtm/version2_1/SRTM3/"; 36 | 37 | /// 38 | /// The continents to try. 39 | /// 40 | public static string[] CONTINENTS = new string[] 41 | { 42 | "Africa", 43 | "Australia", 44 | "Eurasia", 45 | "Islands", 46 | "North_America", 47 | "South_America" 48 | }; 49 | 50 | /// 51 | /// Gets the missing cell. 52 | /// 53 | public bool GetMissingCell(string path, string name) 54 | { 55 | var filename = name + ".hgt.zip"; 56 | var local = System.IO.Path.Combine(path, filename); 57 | 58 | var Logger = LogProvider.For(); 59 | Logger.Info("Downloading {0} ...", name); 60 | foreach (var continent in CONTINENTS) 61 | { 62 | if (SourceHelpers.Download(local, SOURCE + continent + "/" + filename)) 63 | { 64 | return true; 65 | } 66 | } 67 | return false; 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /SRTM.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27004.2009 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4B330D21-2EE5-4C34-B716-0DB1FBC58E29}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C142786B-F60E-4B6B-B9F2-1D25C3A3D7B5}" 9 | ProjectSection(SolutionItems) = preProject 10 | .gitignore = .gitignore 11 | README.md = README.md 12 | EndProjectSection 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{7E594B3F-4CA9-4198-B90D-DD16972D0481}" 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{71C3E43F-7206-435F-8A43-92F79B2777B8}" 17 | EndProject 18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "releasenotes", "releasenotes", "{7B0DAA6C-67CD-4BCD-97AC-B81804EAA2F8}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SRTM", "src\SRTM\SRTM.csproj", "{EB3BE394-DF15-49B8-8BC7-DA56DD0A0D67}" 21 | EndProject 22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SRTM.Tests.Functional", "test\SRTM.Tests.Functional\SRTM.Tests.Functional.csproj", "{0CC09A5A-321C-4A77-B61F-4E3B14A0BACB}" 23 | EndProject 24 | Global 25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 26 | Debug|Any CPU = Debug|Any CPU 27 | Release|Any CPU = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {EB3BE394-DF15-49B8-8BC7-DA56DD0A0D67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {EB3BE394-DF15-49B8-8BC7-DA56DD0A0D67}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {EB3BE394-DF15-49B8-8BC7-DA56DD0A0D67}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {EB3BE394-DF15-49B8-8BC7-DA56DD0A0D67}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {0CC09A5A-321C-4A77-B61F-4E3B14A0BACB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {0CC09A5A-321C-4A77-B61F-4E3B14A0BACB}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {0CC09A5A-321C-4A77-B61F-4E3B14A0BACB}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {0CC09A5A-321C-4A77-B61F-4E3B14A0BACB}.Release|Any CPU.Build.0 = Release|Any CPU 38 | EndGlobalSection 39 | GlobalSection(SolutionProperties) = preSolution 40 | HideSolutionNode = FALSE 41 | EndGlobalSection 42 | GlobalSection(NestedProjects) = preSolution 43 | {7B0DAA6C-67CD-4BCD-97AC-B81804EAA2F8} = {71C3E43F-7206-435F-8A43-92F79B2777B8} 44 | {EB3BE394-DF15-49B8-8BC7-DA56DD0A0D67} = {4B330D21-2EE5-4C34-B716-0DB1FBC58E29} 45 | {0CC09A5A-321C-4A77-B61F-4E3B14A0BACB} = {7E594B3F-4CA9-4198-B90D-DD16972D0481} 46 | EndGlobalSection 47 | GlobalSection(ExtensibilityGlobals) = postSolution 48 | SolutionGuid = {5714010C-59D1-4FFB-A854-0A4511E88222} 49 | EndGlobalSection 50 | EndGlobal 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SRTM 2 | 3 | [![build](https://github.com/itinero/srtm/actions/workflows/build.yml/badge.svg)](https://github.com/itinero/srtm/actions/workflows/build.yml) 4 | [![Join the chat at https://gitter.im/Itinero/Lobby](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Itinero/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 5 | [![Visit our website](https://img.shields.io/badge/website-itinero.tech-020031.svg) ](http://www.itinero.tech/) 6 | [![](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/itinero/srtm/blob/master/LICENSE.md) 7 | 8 | - SRTM: [![NuGet Badge](https://buildstats.info/nuget/SRTM)](https://www.nuget.org/packages/SRTM/) 9 | 10 | A simple library to load SRTM data and return heights in _meter_ for a given lat/lon. Based on [Alpinechough.Srtm 11 | ](https://github.com/alpinechough/Alpinechough.Srtm). 12 | 13 | ## Usage 14 | 15 | ```csharp 16 | // create a new srtm data instance. 17 | // it accepts a folder to download and cache data into in addition to the source you want to use for the data. 18 | // USGS data is immediately available, but is of a lower resolution. 19 | var srtmData = new SRTMData(@"/path/to/data/cache", new USGSSource()); 20 | // NASA data is of a higher resolution, but requires creating an account at https://urs.earthdata.nasa.gov/users/new/. 21 | var credentials = new NetworkCredential("username", "password"); 22 | var srtmData = new SRTMData(@"/path/to/data/cache", new NASASource(credentials)); 23 | 24 | // get elevations for some locations 25 | int? elevation = srtmData.GetElevation(47.267222, 11.392778); 26 | Console.WriteLine("Elevation of Innsbruck: {0}m", elevation); 27 | 28 | elevation = srtmData.GetElevation(-16.5, -68.15); 29 | Console.WriteLine("Elevation of La Paz: {0}m", elevation); 30 | 31 | elevation = srtmData.GetElevation(27.702983735525862f, 85.2978515625f); 32 | Console.WriteLine("Elevation of Kathmandu {0}m", elevation); 33 | 34 | elevation = srtmData.GetElevation(21.030673628606102f, 105.853271484375f); 35 | Console.WriteLine("Elevation of Ha Noi {0}m", elevation); 36 | 37 | // if a smoother result is preferred, it is possible to use bilinear interpolation at the cost of some accuracy 38 | double? smoothElevation = srtmData.GetElevationBilinear(47.267222, 11.392778); 39 | Console.WriteLine("Elevation of Innsbruck: {0}m", elevation); 40 | 41 | smoothElevation = srtmData.GetElevationBilinear(-16.5, -68.15); 42 | Console.WriteLine("Elevation of La Paz: {0}m", elevation); 43 | 44 | smoothElevation = srtmData.GetElevationBilinear(27.702983735525862f, 85.2978515625f); 45 | Console.WriteLine("Elevation of Kathmandu {0}m", elevation); 46 | 47 | smoothElevation = srtmData.GetElevationBilinear(21.030673628606102f, 105.853271484375f); 48 | Console.WriteLine("Elevation of Ha Noi {0}m", elevation); 49 | ``` 50 | 51 | ## Data sources 52 | 53 | We implemented two sources of data, the [USGS SRTM](https://dds.cr.usgs.gov/srtm/version2_1/SRTM3/) and [NASA SRTM](https://e4ftl01.cr.usgs.gov/MEASURES/SRTMGL1.003/). If you want to add an extra source, we're accepting pull requests, you just need to implement [something like this](https://github.com/itinero/srtm/blob/master/src/SRTM/Sources/USGS/USGSSource.cs). 54 | 55 | ## We need help! 56 | 57 | If you think we need to add another source of data **let us know via the issues**, if you know more about SRTM or of another source of elevation, also **let us know**. 58 | 59 | -------------------------------------------------------------------------------- /test/SRTM.Tests.Functional/Program.cs: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | 3 | // Copyright (c) 2017 Alpine Chough Software, Ben Abelshausen 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | using Serilog; 24 | using System; 25 | using SRTM.Sources.USGS; 26 | 27 | namespace SRTM.Tests.Functional 28 | { 29 | class Program 30 | { 31 | static void Main(string[] args) 32 | { 33 | var log = new LoggerConfiguration() 34 | .WriteTo.ColoredConsole(outputTemplate: "{Timestamp:HH:mm} [{Level}] ({Name:l}) {Message}{NewLine}{Exception}") 35 | .CreateLogger(); 36 | Log.Logger = log; 37 | 38 | USGSTest(); 39 | BilinearInterpolationTest(); 40 | 41 | Console.WriteLine("Testing finished."); 42 | Console.ReadLine(); 43 | } 44 | 45 | static void USGSTest() 46 | { 47 | Console.WriteLine("Start USGSTest."); 48 | 49 | // https://dds.cr.usgs.gov/srtm/version2_1/SRTM3/ 50 | var srtmData = new SRTMData(@"srtm-cache", new USGSSource()); 51 | 52 | int? elevationInnsbruck = srtmData.GetElevation(47.267222, 11.392778); 53 | Console.WriteLine("Elevation of Innsbruck: {0}m", elevationInnsbruck); 54 | 55 | int? elevationLaPaz = srtmData.GetElevation(-16.5, -68.15); 56 | Console.WriteLine("Elevation of La Paz: {0}m", elevationLaPaz); 57 | 58 | int? elevationKathmandu = srtmData.GetElevation(27.702983735525862f, 85.2978515625f); 59 | Console.WriteLine("Elevation of Kathmandu {0}m", elevationKathmandu); 60 | 61 | int? elevationHanoi = srtmData.GetElevation(21.030673628606102f, 105.853271484375f); 62 | Console.WriteLine("Elevation of Ha Noi {0}m", elevationHanoi); 63 | 64 | // tries to get elevation from an empty cell. 65 | int? elevationSomeplace1 = srtmData.GetElevation(52.02237f, 2.55853224f); 66 | Console.WriteLine("Elevation of nowhere returns {0}", elevationSomeplace1); 67 | 68 | int? elevationNamibia1 = srtmData.GetElevation(-20, 19.89597); 69 | Console.WriteLine("Elevation of namibia1 returns {0}", elevationNamibia1); 70 | 71 | Console.WriteLine("End USGSTest."); 72 | } 73 | 74 | static void BilinearInterpolationTest() 75 | { 76 | Console.WriteLine("Start BilinearInterpolationTest."); 77 | 78 | // https://dds.cr.usgs.gov/srtm/version2_1/SRTM3/ 79 | var srtmData = new SRTMData(@"srtm-cache", new USGSSource()); 80 | 81 | double? elevationInnsbruck = srtmData.GetElevationBilinear(47.267222, 11.392778); 82 | Console.WriteLine("Bilinear elevation of Innsbruck: {0}m", elevationInnsbruck); 83 | 84 | double? elevationLaPaz = srtmData.GetElevationBilinear(-16.5, -68.15); 85 | Console.WriteLine("Elevation of La Paz: {0}m", elevationLaPaz); 86 | 87 | double? elevationKathmandu = srtmData.GetElevationBilinear(27.702983735525862f, 85.2978515625f); 88 | Console.WriteLine("Elevation of Kathmandu {0}m", elevationKathmandu); 89 | 90 | double? elevationHanoi = srtmData.GetElevationBilinear(21.030673628606102f, 105.853271484375f); 91 | Console.WriteLine("Elevation of Ha Noi {0}m", elevationHanoi); 92 | 93 | // tries to get elevation from an empty cell. 94 | double? elevationSomeplace1 = srtmData.GetElevationBilinear(52.02237f, 2.55853224f); 95 | Console.WriteLine("Elevation of nowhere returns {0}", elevationSomeplace1); 96 | 97 | double? elevationNamibia1 = srtmData.GetElevationBilinear(-20, 19.89597); 98 | Console.WriteLine("Elevation of namibia1 returns {0}", elevationNamibia1); 99 | 100 | //Testing interpolation across cell edges 101 | double? elevationOxted = srtmData.GetElevationBilinear(51.2525, 0.00001); 102 | Console.WriteLine("Elevation of Oxted {0}m", elevationOxted); 103 | 104 | Console.WriteLine("End BilinearInterpolationTest."); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/SRTM/SRTMDataCell.cs: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | 3 | // Copyright (c) 2017 Alpine Chough Software, Ben Abelshausen 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | using System; 24 | using System.IO; 25 | 26 | namespace SRTM 27 | { 28 | /// 29 | /// SRTM data cell. 30 | /// 31 | /// 32 | /// Is thrown when a file path argument specifies a file that does not exist. 33 | /// 34 | /// 35 | /// Is thrown when an argument passed to a method is invalid. 36 | /// 37 | /// 38 | /// Is thrown when an argument passed to a method is invalid because it is outside the allowable range of values as 39 | /// specified by the method. 40 | /// 41 | public class SRTMDataCell : ISRTMDataCell 42 | { 43 | #region Lifecycle 44 | 45 | /// 46 | /// Initializes a new instance of the class. 47 | /// 48 | /// 49 | /// Filepath. 50 | /// 51 | /// 52 | /// Is thrown when a file path argument specifies a file that does not exist. 53 | /// 54 | /// 55 | /// Is thrown when an argument passed to a method is invalid. 56 | /// 57 | public SRTMDataCell(string filepath) 58 | { 59 | if (!File.Exists(filepath)) 60 | throw new FileNotFoundException("File not found.", filepath); 61 | 62 | var filename = Path.GetFileName(filepath); 63 | filename = filename.Substring(0, filename.IndexOf('.')).ToLower(); // Path.GetFileNameWithoutExtension(filepath).ToLower(); 64 | var fileCoordinate = filename.Split(new[] { 'e', 'w' }); 65 | if (fileCoordinate.Length != 2) 66 | throw new ArgumentException("Invalid filename.", filepath); 67 | 68 | fileCoordinate[0] = fileCoordinate[0].TrimStart(new[] { 'n', 's' }); 69 | 70 | Latitude = int.Parse(fileCoordinate[0]); 71 | if (filename.Contains("s")) 72 | Latitude *= -1; 73 | 74 | Longitude = int.Parse(fileCoordinate[1]); 75 | if (filename.Contains("w")) 76 | Longitude *= -1; 77 | 78 | if (filepath.EndsWith(".zip")) 79 | { 80 | using (var stream = File.OpenRead(filepath)) 81 | using (var archive = new System.IO.Compression.ZipArchive(stream)) 82 | using (var memoryStream = new MemoryStream()) 83 | { 84 | using (var hgt = archive.Entries[0].Open()) 85 | { 86 | hgt.CopyTo(memoryStream); 87 | HgtData = memoryStream.ToArray(); 88 | } 89 | } 90 | } 91 | else 92 | { 93 | HgtData = File.ReadAllBytes(filepath); 94 | } 95 | 96 | switch (HgtData.Length) 97 | { 98 | case 1201 * 1201 * 2: // SRTM-3 99 | PointsPerCell = 1201; 100 | break; 101 | case 3601 * 3601 * 2: // SRTM-1 102 | PointsPerCell = 3601; 103 | break; 104 | default: 105 | throw new ArgumentException("Invalid file size.", filepath); 106 | } 107 | } 108 | 109 | #endregion 110 | 111 | #region Properties 112 | 113 | /// 114 | /// Gets or sets the hgt data. 115 | /// 116 | /// 117 | /// The hgt data. 118 | /// 119 | private byte[] HgtData { get; set; } 120 | 121 | /// 122 | /// Gets or sets the points per cell. 123 | /// 124 | /// 125 | /// The points per cell. 126 | /// 127 | private int PointsPerCell { get; set; } 128 | 129 | /// 130 | /// Gets or sets the latitude of the srtm data file. 131 | /// 132 | /// 133 | /// The latitude. 134 | /// 135 | public int Latitude { get; private set; } 136 | 137 | /// 138 | /// Gets or sets the longitude of the srtm data file. 139 | /// 140 | /// 141 | /// The longitude. 142 | /// 143 | public int Longitude { get; private set; } 144 | 145 | #endregion 146 | 147 | #region Public Methods 148 | 149 | /// 150 | /// Gets the elevation. 151 | /// 152 | /// 153 | /// The height. Null, if elevation is not available. 154 | /// 155 | /// 156 | /// 157 | /// 158 | /// Represents errors that occur during application execution. 159 | /// 160 | public int? GetElevation(double latitude, double longitude) 161 | { 162 | int localLat = (int)((latitude - Latitude) * PointsPerCell); 163 | int localLon = (int)(((longitude - Longitude)) * PointsPerCell); 164 | return ReadByteData(localLat, localLon); 165 | } 166 | 167 | /// 168 | /// Gets the elevation. Data is smoothed using bilinear interpolation. 169 | /// 170 | /// 171 | /// The height. Null, if elevation is not available. 172 | /// 173 | /// 174 | /// 175 | /// 176 | /// Represents errors that occur during application execution. 177 | /// 178 | public double? GetElevationBilinear(double latitude, double longitude) 179 | { 180 | double localLat = (latitude - Latitude) * PointsPerCell; 181 | double localLon = (longitude - Longitude) * PointsPerCell; 182 | 183 | int localLatMin = (int) Math.Floor(localLat); 184 | int localLonMin = (int) Math.Floor(localLon); 185 | int localLatMax = (int) Math.Ceiling(localLat); 186 | int localLonMax = (int) Math.Ceiling(localLon); 187 | 188 | int? elevation00 = ReadByteData(localLatMin, localLonMin); 189 | int? elevation10 = ReadByteData(localLatMax, localLonMin); 190 | int? elevation01 = ReadByteData(localLatMin, localLonMax); 191 | int? elevation11 = ReadByteData(localLatMax, localLonMax); 192 | 193 | if (!elevation00.HasValue || !elevation10.HasValue || !elevation01.HasValue || !elevation11.HasValue) 194 | { 195 | //Can't do bilinear if missing one of the points. Default to regular. 196 | return (double)GetElevation(latitude, longitude); 197 | } 198 | 199 | double deltaLat = localLatMax - localLat; 200 | double deltaLon = localLonMax - localLon; 201 | 202 | return Blerp((double) elevation00, (double) elevation10, (double) elevation01, (double) elevation11, 203 | deltaLat, deltaLon); 204 | } 205 | 206 | #endregion 207 | 208 | #region Private Methods 209 | 210 | /// 211 | /// Method responsible for reading byte data from data cell file. 212 | /// 213 | /// Local latitude within the data cell 214 | /// Local longitude within the data cell 215 | /// Height read from data cell file 216 | /// 217 | private int? ReadByteData(int localLat, int localLon) 218 | { 219 | int bytesPos = ((PointsPerCell - localLat - 1) * PointsPerCell * 2) + localLon * 2; 220 | 221 | if (bytesPos < 0 || bytesPos > PointsPerCell * PointsPerCell * 2) 222 | throw new ArgumentOutOfRangeException("Coordinates out of range.", "coordinates"); 223 | 224 | if (bytesPos >= HgtData.Length) 225 | return null; 226 | 227 | if ((HgtData[bytesPos] == 0x80) && (HgtData[bytesPos + 1] == 0x00)) 228 | return null; 229 | 230 | // Motorola "big-endian" order with the most significant byte first 231 | return (HgtData[bytesPos]) << 8 | HgtData[bytesPos + 1]; 232 | } 233 | 234 | private double Lerp(double start, double end, double delta) 235 | { 236 | return start + (end - start) * delta; 237 | } 238 | 239 | private double Blerp(double val00, double val10, double val01, double val11, double deltaX, double deltaY) 240 | { 241 | return Lerp(Lerp(val11, val01, deltaX), Lerp(val10, val00, deltaX), deltaY); 242 | } 243 | 244 | #endregion 245 | } 246 | } -------------------------------------------------------------------------------- /src/SRTM/SRTMData.cs: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | 3 | // Copyright (c) 2017 Alpine Chough Software, Ben Abelshausen 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.IO; 26 | using System.Linq; 27 | using SRTM.Sources; 28 | 29 | namespace SRTM 30 | { 31 | /// 32 | /// SRTM Data. 33 | /// 34 | /// 35 | /// Is thrown when part of a file or directory argument cannot be found. 36 | /// 37 | public class SRTMData : ISRTMData 38 | { 39 | private const int RETRIES = 3; 40 | private ISRTMSource _source; 41 | 42 | /// 43 | /// Initializes a new instance of the class. 44 | /// 45 | /// 46 | /// Data directory. 47 | /// 48 | /// 49 | /// Data source to use. Must be an instance of the class 50 | /// 51 | /// 52 | /// Is thrown when part of a file or directory argument cannot be found. 53 | /// 54 | public SRTMData(string dataDirectory, ISRTMSource source) 55 | { 56 | if (!Directory.Exists(dataDirectory)) 57 | throw new DirectoryNotFoundException(dataDirectory); 58 | 59 | _source = source; 60 | GetMissingCell = _source.GetMissingCell; 61 | DataDirectory = dataDirectory; 62 | DataCells = new List(); 63 | } 64 | 65 | /// 66 | /// Initializes a new instance of the class. 67 | /// 68 | /// 69 | /// Data directory. 70 | /// 71 | /// 72 | /// Gets a missing cell and writes it to the given path. 73 | /// 74 | /// 75 | /// Is thrown when part of a file or directory argument cannot be found. 76 | /// 77 | public SRTMData(string dataDirectory, Func<(string path, string name), bool> getMissingCell) 78 | : this(dataDirectory, new FuncSource(getMissingCell)) 79 | { 80 | 81 | } 82 | 83 | /// 84 | /// A delegate to get missing cells. 85 | /// 86 | public delegate bool GetMissingCellDelegate(string path, string name); 87 | 88 | /// 89 | /// Gets or sets the missing cell delegate. 90 | /// 91 | public GetMissingCellDelegate GetMissingCell { get; set; } 92 | 93 | /// 94 | /// Gets or sets the data directory. 95 | /// 96 | /// 97 | /// The data directory. 98 | /// 99 | public string DataDirectory { get; private set; } 100 | 101 | /// 102 | /// Gets or sets the SRTM data cells. 103 | /// 104 | /// 105 | /// The SRTM data cells. 106 | /// 107 | private List DataCells { get; set; } 108 | 109 | #region Public methods 110 | 111 | /// 112 | /// Unloads all SRTM data cells. 113 | /// 114 | public void Unload() 115 | { 116 | DataCells.Clear(); 117 | } 118 | 119 | /// 120 | /// Gets the elevation. 121 | /// 122 | /// 123 | /// The height. Null, if elevation is not available. 124 | /// 125 | /// 126 | /// Latitude in decimal degrees of desired location. 127 | /// 128 | /// 129 | /// Longitude in decimal degrees of desired location 130 | /// 131 | /// 132 | /// Represents errors that occur during application execution. 133 | /// 134 | public int? GetElevation(double latitude, double longitude) 135 | { 136 | ISRTMDataCell dataCell = GetDataCell(latitude, longitude); 137 | return dataCell.GetElevation(latitude, longitude); 138 | } 139 | 140 | /// 141 | /// Gets the elevation. Data is smoothed using bilinear interpolation. 142 | /// 143 | /// 144 | /// The height. Null, if elevation is not available. 145 | /// 146 | /// 147 | /// Latitude in decimal degrees of desired location. 148 | /// 149 | /// 150 | /// Longitude in decimal degrees of desired location 151 | /// 152 | /// 153 | /// Represents errors that occur during application execution. 154 | /// 155 | public double? GetElevationBilinear(double latitude, double longitude) 156 | { 157 | ISRTMDataCell dataCell = GetDataCell(latitude, longitude); 158 | return dataCell.GetElevationBilinear(latitude, longitude); 159 | } 160 | 161 | #endregion 162 | 163 | #region Private methods 164 | 165 | /// 166 | /// Method responsible for identifying the correct data cell and either retrieving it from cache ir downloading it from source. 167 | /// 168 | /// 169 | /// 170 | /// Elevation data cell. Must be an instance of the class. 171 | private ISRTMDataCell GetDataCell(double latitude, double longitude) 172 | { 173 | int cellLatitude = (int)Math.Floor(Math.Abs(latitude)); 174 | if (latitude < 0) 175 | { 176 | cellLatitude *= -1; 177 | if (cellLatitude != latitude) 178 | { // if exactly equal, keep the current tile. 179 | cellLatitude -= 1; // because negative so in bottom tile 180 | } 181 | } 182 | 183 | int cellLongitude = (int)Math.Floor(Math.Abs(longitude)); 184 | if (longitude < 0) 185 | { 186 | cellLongitude *= -1; 187 | if (cellLongitude != longitude) 188 | { // if exactly equal, keep the current tile. 189 | cellLongitude -= 1; // because negative so in left tile 190 | } 191 | } 192 | 193 | var dataCell = DataCells.Where(dc => dc.Latitude == cellLatitude && dc.Longitude == cellLongitude).FirstOrDefault(); 194 | if (dataCell != null) 195 | { 196 | return dataCell; 197 | } 198 | 199 | string filename = string.Format("{0}{1:D2}{2}{3:D3}", 200 | cellLatitude < 0 ? "S" : "N", 201 | Math.Abs(cellLatitude), 202 | cellLongitude < 0 ? "W" : "E", 203 | Math.Abs(cellLongitude)); 204 | 205 | var filePath = Path.Combine(DataDirectory, filename + ".hgt"); 206 | var zipFilePath = Path.Combine(DataDirectory, filename + ".hgt.zip"); 207 | var txtFilePath = Path.Combine(DataDirectory, filename + ".txt"); 208 | var count = -1; 209 | 210 | if (!File.Exists(filePath) && !File.Exists(zipFilePath) && !File.Exists(txtFilePath) && 211 | this.GetMissingCell != null) 212 | { 213 | this.GetMissingCell(DataDirectory, filename); 214 | } 215 | else if(File.Exists(txtFilePath) && this.GetMissingCell != null) 216 | { 217 | var txtFile = File.ReadAllText(txtFilePath); 218 | if (!int.TryParse(txtFile, out count)) 219 | { 220 | File.Delete(txtFilePath); 221 | count = -1; 222 | } 223 | else if(count < RETRIES) 224 | { 225 | if (this.GetMissingCell(DataDirectory, filename)) 226 | { 227 | File.Delete(txtFilePath); 228 | } 229 | } 230 | } 231 | 232 | if (File.Exists(filePath)) 233 | { 234 | dataCell = new SRTMDataCell(filePath); 235 | } 236 | else if(File.Exists(zipFilePath)) 237 | { 238 | dataCell = new SRTMDataCell(zipFilePath); 239 | } 240 | else 241 | { 242 | if (count < 0) 243 | { 244 | File.WriteAllText(txtFilePath, "1"); 245 | return GetDataCell(latitude, longitude); 246 | } 247 | else if (count < RETRIES) 248 | { 249 | count++; 250 | File.WriteAllText(txtFilePath, count.ToString()); 251 | return GetDataCell(latitude, longitude); 252 | } 253 | else 254 | { 255 | dataCell = new EmptySRTMDataCell(txtFilePath); 256 | } 257 | } 258 | DataCells.Add(dataCell); 259 | 260 | return dataCell; 261 | } 262 | 263 | #endregion 264 | } 265 | } -------------------------------------------------------------------------------- /src/SRTM/Logging/LibLog.cs: -------------------------------------------------------------------------------- 1 | // 2 | //=============================================================================== 3 | // LibLog 4 | // 5 | // https://github.com/damianh/LibLog 6 | //=============================================================================== 7 | // Copyright © 2011-2017 Damian Hickey. All rights reserved. 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | //=============================================================================== 27 | 28 | // ReSharper disable PossibleNullReferenceException 29 | 30 | // Define LIBLOG_PORTABLE conditional compilation symbol for PCL compatibility 31 | // 32 | // Define LIBLOG_PUBLIC to enable ability to GET a logger (LogProvider.For<>() etc) from outside this library. NOTE: 33 | // this can have unintended consequences of consumers of your library using your library to resolve a logger. If the 34 | // reason is because you want to open this functionality to other projects within your solution, 35 | // consider [InternalsVisibleTo] instead. 36 | // 37 | // Define LIBLOG_PROVIDERS_ONLY if your library provides its own logging API and you just want to use the 38 | // LibLog providers internally to provide built in support for popular logging frameworks. 39 | 40 | #pragma warning disable 1591 41 | 42 | using System.Diagnostics.CodeAnalysis; 43 | 44 | [assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "YourRootNamespace.Logging")] 45 | [assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "YourRootNamespace.Logging.Logger.#Invoke(YourRootNamespace.Logging.LogLevel,System.Func`1,System.Exception,System.Object[])")] 46 | 47 | // If you copied this file manually, you need to change all "YourRootNameSpace" so not to clash with other libraries 48 | // that use LibLog 49 | #if LIBLOG_PROVIDERS_ONLY 50 | namespace YourRootNamespace.LibLog 51 | #else 52 | namespace SRTM.Logging 53 | #endif 54 | { 55 | using System.Collections.Generic; 56 | using System.Diagnostics.CodeAnalysis; 57 | #if LIBLOG_PROVIDERS_ONLY 58 | using SRTM.LibLog.LogProviders; 59 | #else 60 | using SRTM.Logging.LogProviders; 61 | #endif 62 | using System; 63 | #if !LIBLOG_PROVIDERS_ONLY 64 | using System.Diagnostics; 65 | #if !LIBLOG_PORTABLE 66 | using System.Runtime.CompilerServices; 67 | #endif 68 | #endif 69 | 70 | #if LIBLOG_PROVIDERS_ONLY 71 | internal 72 | #else 73 | public 74 | #endif 75 | delegate bool Logger(LogLevel logLevel, Func messageFunc, Exception exception = null, params object[] formatParameters); 76 | 77 | #if !LIBLOG_PROVIDERS_ONLY 78 | /// 79 | /// Simple interface that represent a logger. 80 | /// 81 | #if LIBLOG_PUBLIC 82 | public 83 | #else 84 | internal 85 | #endif 86 | interface ILog 87 | { 88 | /// 89 | /// Log a message the specified log level. 90 | /// 91 | /// The log level. 92 | /// The message function. 93 | /// An optional exception. 94 | /// Optional format parameters for the message generated by the messagefunc. 95 | /// true if the message was logged. Otherwise false. 96 | /// 97 | /// Note to implementers: the message func should not be called if the loglevel is not enabled 98 | /// so as not to incur performance penalties. 99 | /// 100 | /// To check IsEnabled call Log with only LogLevel and check the return value, no event will be written. 101 | /// 102 | bool Log(LogLevel logLevel, Func messageFunc, Exception exception = null, params object[] formatParameters); 103 | } 104 | #endif 105 | 106 | /// 107 | /// The log level. 108 | /// 109 | #if LIBLOG_PROVIDERS_ONLY 110 | internal 111 | #else 112 | public 113 | #endif 114 | enum LogLevel 115 | { 116 | Trace, 117 | Debug, 118 | Info, 119 | Warn, 120 | Error, 121 | Fatal 122 | } 123 | 124 | #if !LIBLOG_PROVIDERS_ONLY 125 | #if !LIBLOG_PORTABLE 126 | [ExcludeFromCodeCoverage] 127 | #endif 128 | #if LIBLOG_PUBLIC 129 | public 130 | #else 131 | internal 132 | #endif 133 | static partial class LogExtensions 134 | { 135 | public static bool IsDebugEnabled(this ILog logger) 136 | { 137 | GuardAgainstNullLogger(logger); 138 | return logger.Log(LogLevel.Debug, null); 139 | } 140 | 141 | public static bool IsErrorEnabled(this ILog logger) 142 | { 143 | GuardAgainstNullLogger(logger); 144 | return logger.Log(LogLevel.Error, null); 145 | } 146 | 147 | public static bool IsFatalEnabled(this ILog logger) 148 | { 149 | GuardAgainstNullLogger(logger); 150 | return logger.Log(LogLevel.Fatal, null); 151 | } 152 | 153 | public static bool IsInfoEnabled(this ILog logger) 154 | { 155 | GuardAgainstNullLogger(logger); 156 | return logger.Log(LogLevel.Info, null); 157 | } 158 | 159 | public static bool IsTraceEnabled(this ILog logger) 160 | { 161 | GuardAgainstNullLogger(logger); 162 | return logger.Log(LogLevel.Trace, null); 163 | } 164 | 165 | public static bool IsWarnEnabled(this ILog logger) 166 | { 167 | GuardAgainstNullLogger(logger); 168 | return logger.Log(LogLevel.Warn, null); 169 | } 170 | 171 | public static void Debug(this ILog logger, Func messageFunc) 172 | { 173 | GuardAgainstNullLogger(logger); 174 | logger.Log(LogLevel.Debug, WrapLogInternal(messageFunc)); 175 | } 176 | 177 | public static void Debug(this ILog logger, string message) 178 | { 179 | if (logger.IsDebugEnabled()) 180 | { 181 | logger.Log(LogLevel.Debug, message.AsFunc()); 182 | } 183 | } 184 | 185 | public static void Debug(this ILog logger, string message, params object[] args) 186 | { 187 | logger.DebugFormat(message, args); 188 | } 189 | 190 | public static void Debug(this ILog logger, Exception exception, string message, params object[] args) 191 | { 192 | logger.DebugException(message, exception, args); 193 | } 194 | 195 | public static void DebugFormat(this ILog logger, string message, params object[] args) 196 | { 197 | if (logger.IsDebugEnabled()) 198 | { 199 | logger.LogFormat(LogLevel.Debug, message, args); 200 | } 201 | } 202 | 203 | public static void DebugException(this ILog logger, string message, Exception exception) 204 | { 205 | if (logger.IsDebugEnabled()) 206 | { 207 | logger.Log(LogLevel.Debug, message.AsFunc(), exception); 208 | } 209 | } 210 | 211 | public static void DebugException(this ILog logger, string message, Exception exception, params object[] formatParams) 212 | { 213 | if (logger.IsDebugEnabled()) 214 | { 215 | logger.Log(LogLevel.Debug, message.AsFunc(), exception, formatParams); 216 | } 217 | } 218 | 219 | public static void Error(this ILog logger, Func messageFunc) 220 | { 221 | GuardAgainstNullLogger(logger); 222 | logger.Log(LogLevel.Error, WrapLogInternal(messageFunc)); 223 | } 224 | 225 | public static void Error(this ILog logger, string message) 226 | { 227 | if (logger.IsErrorEnabled()) 228 | { 229 | logger.Log(LogLevel.Error, message.AsFunc()); 230 | } 231 | } 232 | 233 | public static void Error(this ILog logger, string message, params object[] args) 234 | { 235 | logger.ErrorFormat(message, args); 236 | } 237 | 238 | public static void Error(this ILog logger, Exception exception, string message, params object[] args) 239 | { 240 | logger.ErrorException(message, exception, args); 241 | } 242 | 243 | public static void ErrorFormat(this ILog logger, string message, params object[] args) 244 | { 245 | if (logger.IsErrorEnabled()) 246 | { 247 | logger.LogFormat(LogLevel.Error, message, args); 248 | } 249 | } 250 | 251 | public static void ErrorException(this ILog logger, string message, Exception exception, params object[] formatParams) 252 | { 253 | if (logger.IsErrorEnabled()) 254 | { 255 | logger.Log(LogLevel.Error, message.AsFunc(), exception, formatParams); 256 | } 257 | } 258 | 259 | public static void Fatal(this ILog logger, Func messageFunc) 260 | { 261 | logger.Log(LogLevel.Fatal, WrapLogInternal(messageFunc)); 262 | } 263 | 264 | public static void Fatal(this ILog logger, string message) 265 | { 266 | if (logger.IsFatalEnabled()) 267 | { 268 | logger.Log(LogLevel.Fatal, message.AsFunc()); 269 | } 270 | } 271 | 272 | public static void Fatal(this ILog logger, string message, params object[] args) 273 | { 274 | logger.FatalFormat(message, args); 275 | } 276 | 277 | public static void Fatal(this ILog logger, Exception exception, string message, params object[] args) 278 | { 279 | logger.FatalException(message, exception, args); 280 | } 281 | 282 | public static void FatalFormat(this ILog logger, string message, params object[] args) 283 | { 284 | if (logger.IsFatalEnabled()) 285 | { 286 | logger.LogFormat(LogLevel.Fatal, message, args); 287 | } 288 | } 289 | 290 | public static void FatalException(this ILog logger, string message, Exception exception, params object[] formatParams) 291 | { 292 | if (logger.IsFatalEnabled()) 293 | { 294 | logger.Log(LogLevel.Fatal, message.AsFunc(), exception, formatParams); 295 | } 296 | } 297 | 298 | public static void Info(this ILog logger, Func messageFunc) 299 | { 300 | GuardAgainstNullLogger(logger); 301 | logger.Log(LogLevel.Info, WrapLogInternal(messageFunc)); 302 | } 303 | 304 | public static void Info(this ILog logger, string message) 305 | { 306 | if (logger.IsInfoEnabled()) 307 | { 308 | logger.Log(LogLevel.Info, message.AsFunc()); 309 | } 310 | } 311 | 312 | public static void Info(this ILog logger, string message, params object[] args) 313 | { 314 | logger.InfoFormat(message, args); 315 | } 316 | 317 | public static void Info(this ILog logger, Exception exception, string message, params object[] args) 318 | { 319 | logger.InfoException(message, exception, args); 320 | } 321 | 322 | public static void InfoFormat(this ILog logger, string message, params object[] args) 323 | { 324 | if (logger.IsInfoEnabled()) 325 | { 326 | logger.LogFormat(LogLevel.Info, message, args); 327 | } 328 | } 329 | 330 | public static void InfoException(this ILog logger, string message, Exception exception, params object[] formatParams) 331 | { 332 | if (logger.IsInfoEnabled()) 333 | { 334 | logger.Log(LogLevel.Info, message.AsFunc(), exception, formatParams); 335 | } 336 | } 337 | 338 | public static void Trace(this ILog logger, Func messageFunc) 339 | { 340 | GuardAgainstNullLogger(logger); 341 | logger.Log(LogLevel.Trace, WrapLogInternal(messageFunc)); 342 | } 343 | 344 | public static void Trace(this ILog logger, string message) 345 | { 346 | if (logger.IsTraceEnabled()) 347 | { 348 | logger.Log(LogLevel.Trace, message.AsFunc()); 349 | } 350 | } 351 | 352 | public static void Trace(this ILog logger, string message, params object[] args) 353 | { 354 | logger.TraceFormat(message, args); 355 | } 356 | 357 | public static void Trace(this ILog logger, Exception exception, string message, params object[] args) 358 | { 359 | logger.TraceException(message, exception, args); 360 | } 361 | 362 | public static void TraceFormat(this ILog logger, string message, params object[] args) 363 | { 364 | if (logger.IsTraceEnabled()) 365 | { 366 | logger.LogFormat(LogLevel.Trace, message, args); 367 | } 368 | } 369 | 370 | public static void TraceException(this ILog logger, string message, Exception exception, params object[] formatParams) 371 | { 372 | if (logger.IsTraceEnabled()) 373 | { 374 | logger.Log(LogLevel.Trace, message.AsFunc(), exception, formatParams); 375 | } 376 | } 377 | 378 | public static void Warn(this ILog logger, Func messageFunc) 379 | { 380 | GuardAgainstNullLogger(logger); 381 | logger.Log(LogLevel.Warn, WrapLogInternal(messageFunc)); 382 | } 383 | 384 | public static void Warn(this ILog logger, string message) 385 | { 386 | if (logger.IsWarnEnabled()) 387 | { 388 | logger.Log(LogLevel.Warn, message.AsFunc()); 389 | } 390 | } 391 | 392 | public static void Warn(this ILog logger, string message, params object[] args) 393 | { 394 | logger.WarnFormat(message, args); 395 | } 396 | 397 | public static void Warn(this ILog logger, Exception exception, string message, params object[] args) 398 | { 399 | logger.WarnException(message, exception, args); 400 | } 401 | 402 | public static void WarnFormat(this ILog logger, string message, params object[] args) 403 | { 404 | if (logger.IsWarnEnabled()) 405 | { 406 | logger.LogFormat(LogLevel.Warn, message, args); 407 | } 408 | } 409 | 410 | public static void WarnException(this ILog logger, string message, Exception exception, params object[] formatParams) 411 | { 412 | if (logger.IsWarnEnabled()) 413 | { 414 | logger.Log(LogLevel.Warn, message.AsFunc(), exception, formatParams); 415 | } 416 | } 417 | 418 | // ReSharper disable once UnusedParameter.Local 419 | private static void GuardAgainstNullLogger(ILog logger) 420 | { 421 | if (logger == null) 422 | { 423 | throw new ArgumentNullException("logger"); 424 | } 425 | } 426 | 427 | private static void LogFormat(this ILog logger, LogLevel logLevel, string message, params object[] args) 428 | { 429 | logger.Log(logLevel, message.AsFunc(), null, args); 430 | } 431 | 432 | // Avoid the closure allocation, see https://gist.github.com/AArnott/d285feef75c18f6ecd2b 433 | private static Func AsFunc(this T value) where T : class 434 | { 435 | return value.Return; 436 | } 437 | 438 | private static T Return(this T value) 439 | { 440 | return value; 441 | } 442 | 443 | // Allow passing callsite-logger-type to LogProviderBase using messageFunc 444 | internal static Func WrapLogSafeInternal(LoggerExecutionWrapper logger, Func messageFunc) 445 | { 446 | Func wrappedMessageFunc = () => 447 | { 448 | try 449 | { 450 | return messageFunc(); 451 | } 452 | catch (Exception ex) 453 | { 454 | logger.WrappedLogger(LogLevel.Error, () => LoggerExecutionWrapper.FailedToGenerateLogMessage, ex); 455 | } 456 | return null; 457 | }; 458 | return wrappedMessageFunc; 459 | } 460 | 461 | // Allow passing callsite-logger-type to LogProviderBase using messageFunc 462 | private static Func WrapLogInternal(Func messageFunc) 463 | { 464 | Func wrappedMessageFunc = () => 465 | { 466 | return messageFunc(); 467 | }; 468 | return wrappedMessageFunc; 469 | } 470 | } 471 | #endif 472 | 473 | /// 474 | /// Represents a way to get a 475 | /// 476 | #if LIBLOG_PROVIDERS_ONLY 477 | internal 478 | #else 479 | public 480 | #endif 481 | interface ILogProvider 482 | { 483 | /// 484 | /// Gets the specified named logger. 485 | /// 486 | /// Name of the logger. 487 | /// The logger reference. 488 | Logger GetLogger(string name); 489 | 490 | /// 491 | /// Opens a nested diagnostics context. Not supported in EntLib logging. 492 | /// 493 | /// The message to add to the diagnostics context. 494 | /// A disposable that when disposed removes the message from the context. 495 | IDisposable OpenNestedContext(string message); 496 | 497 | /// 498 | /// Opens a mapped diagnostics context. Not supported in EntLib logging. 499 | /// 500 | /// A key. 501 | /// A value. 502 | /// A disposable that when disposed removes the map from the context. 503 | IDisposable OpenMappedContext(string key, object value, bool destructure = false); 504 | } 505 | 506 | /// 507 | /// Provides a mechanism to create instances of objects. 508 | /// 509 | #if !LIBLOG_PORTABLE 510 | [ExcludeFromCodeCoverage] 511 | #endif 512 | #if LIBLOG_PROVIDERS_ONLY 513 | internal 514 | #else 515 | public 516 | #endif 517 | static class LogProvider 518 | { 519 | #if !LIBLOG_PROVIDERS_ONLY 520 | private const string NullLogProvider = "Current Log Provider is not set. Call SetCurrentLogProvider " + 521 | "with a non-null value first."; 522 | private static dynamic s_currentLogProvider; 523 | private static Action s_onCurrentLogProviderSet; 524 | private static Lazy s_resolvedLogProvider = new Lazy(() => ForceResolveLogProvider()); 525 | 526 | [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] 527 | static LogProvider() 528 | { 529 | IsDisabled = false; 530 | } 531 | 532 | /// 533 | /// Sets the current log provider. 534 | /// 535 | /// The log provider. 536 | public static void SetCurrentLogProvider(ILogProvider logProvider) 537 | { 538 | s_currentLogProvider = logProvider; 539 | 540 | RaiseOnCurrentLogProviderSet(); 541 | } 542 | 543 | /// 544 | /// Gets or sets a value indicating whether this is logging is disabled. 545 | /// 546 | /// 547 | /// true if logging is disabled; otherwise, false. 548 | /// 549 | public static bool IsDisabled { get; set; } 550 | 551 | /// 552 | /// Sets an action that is invoked when a consumer of your library has called SetCurrentLogProvider. It is 553 | /// important that hook into this if you are using child libraries (especially ilmerged ones) that are using 554 | /// LibLog (or other logging abstraction) so you adapt and delegate to them. 555 | /// 556 | /// 557 | internal static Action OnCurrentLogProviderSet 558 | { 559 | set 560 | { 561 | s_onCurrentLogProviderSet = value; 562 | RaiseOnCurrentLogProviderSet(); 563 | } 564 | } 565 | 566 | internal static ILogProvider CurrentLogProvider 567 | { 568 | get 569 | { 570 | return s_currentLogProvider; 571 | } 572 | } 573 | 574 | /// 575 | /// Gets a logger for the specified type. 576 | /// 577 | /// The type whose name will be used for the logger. 578 | /// An instance of 579 | #if LIBLOG_PUBLIC 580 | public 581 | #else 582 | internal 583 | #endif 584 | static ILog For() 585 | { 586 | return GetLogger(typeof(T)); 587 | } 588 | 589 | #if !LIBLOG_PORTABLE 590 | /// 591 | /// Gets a logger for the current class. 592 | /// 593 | /// An instance of 594 | [MethodImpl(MethodImplOptions.NoInlining)] 595 | #if LIBLOG_PUBLIC 596 | public 597 | #else 598 | internal 599 | #endif 600 | static ILog GetCurrentClassLogger() 601 | { 602 | var stackFrame = new StackFrame(1, false); 603 | return GetLogger(stackFrame.GetMethod().DeclaringType); 604 | } 605 | #endif 606 | 607 | /// 608 | /// Gets a logger for the specified type. 609 | /// 610 | /// The type whose name will be used for the logger. 611 | /// If the type is null then this name will be used as the log name instead 612 | /// An instance of 613 | #if LIBLOG_PUBLIC 614 | public 615 | #else 616 | internal 617 | #endif 618 | static ILog GetLogger(Type type, string fallbackTypeName = "System.Object") 619 | { 620 | // If the type passed in is null then fallback to the type name specified 621 | return GetLogger(type != null ? type.FullName : fallbackTypeName); 622 | } 623 | 624 | /// 625 | /// Gets a logger with the specified name. 626 | /// 627 | /// The name. 628 | /// An instance of 629 | #if LIBLOG_PUBLIC 630 | public 631 | #else 632 | internal 633 | #endif 634 | static ILog GetLogger(string name) 635 | { 636 | ILogProvider logProvider = CurrentLogProvider ?? ResolveLogProvider(); 637 | return logProvider == null 638 | ? NoOpLogger.Instance 639 | : (ILog)new LoggerExecutionWrapper(logProvider.GetLogger(name), () => IsDisabled); 640 | } 641 | 642 | /// 643 | /// Opens a nested diagnostics context. 644 | /// 645 | /// A message. 646 | /// An that closes context when disposed. 647 | [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "SetCurrentLogProvider")] 648 | #if LIBLOG_PUBLIC 649 | public 650 | #else 651 | internal 652 | #endif 653 | static IDisposable OpenNestedContext(string message) 654 | { 655 | ILogProvider logProvider = CurrentLogProvider ?? ResolveLogProvider(); 656 | 657 | return logProvider == null 658 | ? new DisposableAction(() => { }) 659 | : logProvider.OpenNestedContext(message); 660 | } 661 | 662 | /// 663 | /// Opens a mapped diagnostics context. 664 | /// 665 | /// A key. 666 | /// A value. 667 | /// An that closes context when disposed. 668 | [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "SetCurrentLogProvider")] 669 | #if LIBLOG_PUBLIC 670 | public 671 | #else 672 | internal 673 | #endif 674 | static IDisposable OpenMappedContext(string key, object value, bool destructure = false) 675 | { 676 | ILogProvider logProvider = CurrentLogProvider ?? ResolveLogProvider(); 677 | 678 | return logProvider == null 679 | ? new DisposableAction(() => { }) 680 | : logProvider.OpenMappedContext(key, value, destructure); 681 | } 682 | #endif 683 | 684 | #if LIBLOG_PROVIDERS_ONLY 685 | private 686 | #else 687 | internal 688 | #endif 689 | delegate bool IsLoggerAvailable(); 690 | 691 | #if LIBLOG_PROVIDERS_ONLY 692 | private 693 | #else 694 | internal 695 | #endif 696 | delegate ILogProvider CreateLogProvider(); 697 | 698 | #if LIBLOG_PROVIDERS_ONLY 699 | private 700 | #else 701 | internal 702 | #endif 703 | static readonly List> LogProviderResolvers = 704 | new List> 705 | { 706 | new Tuple(SerilogLogProvider.IsLoggerAvailable, () => new SerilogLogProvider()), 707 | new Tuple(NLogLogProvider.IsLoggerAvailable, () => new NLogLogProvider()), 708 | new Tuple(Log4NetLogProvider.IsLoggerAvailable, () => new Log4NetLogProvider()), 709 | new Tuple(EntLibLogProvider.IsLoggerAvailable, () => new EntLibLogProvider()), 710 | new Tuple(LoupeLogProvider.IsLoggerAvailable, () => new LoupeLogProvider()), 711 | }; 712 | 713 | #if !LIBLOG_PROVIDERS_ONLY 714 | private static void RaiseOnCurrentLogProviderSet() 715 | { 716 | if (s_onCurrentLogProviderSet != null) 717 | { 718 | s_onCurrentLogProviderSet(s_currentLogProvider); 719 | } 720 | } 721 | #endif 722 | 723 | internal static ILogProvider ResolveLogProvider() 724 | { 725 | return s_resolvedLogProvider.Value; 726 | } 727 | 728 | [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Console.WriteLine(System.String,System.Object,System.Object)")] 729 | [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] 730 | internal static ILogProvider ForceResolveLogProvider() 731 | { 732 | try 733 | { 734 | foreach (var providerResolver in LogProviderResolvers) 735 | { 736 | if (providerResolver.Item1()) 737 | { 738 | return providerResolver.Item2(); 739 | } 740 | } 741 | } 742 | catch (Exception ex) 743 | { 744 | #if LIBLOG_PORTABLE 745 | Debug.WriteLine( 746 | #else 747 | Console.WriteLine( 748 | #endif 749 | "Exception occurred resolving a log provider. Logging for this assembly {0} is disabled. {1}", 750 | typeof(LogProvider).GetAssemblyPortable().FullName, 751 | ex); 752 | } 753 | return null; 754 | } 755 | 756 | #if !LIBLOG_PROVIDERS_ONLY 757 | #if !LIBLOG_PORTABLE 758 | [ExcludeFromCodeCoverage] 759 | #endif 760 | internal class NoOpLogger : ILog 761 | { 762 | internal static readonly NoOpLogger Instance = new NoOpLogger(); 763 | 764 | public bool Log(LogLevel logLevel, Func messageFunc, Exception exception, params object[] formatParameters) 765 | { 766 | return false; 767 | } 768 | } 769 | #endif 770 | } 771 | 772 | #if !LIBLOG_PROVIDERS_ONLY 773 | #if !LIBLOG_PORTABLE 774 | [ExcludeFromCodeCoverage] 775 | #endif 776 | internal class LoggerExecutionWrapper : ILog 777 | { 778 | private readonly Logger _logger; 779 | private readonly ICallSiteExtension _callsiteLogger; 780 | private readonly Func _getIsDisabled; 781 | internal const string FailedToGenerateLogMessage = "Failed to generate log message"; 782 | 783 | Func _lastExtensionMethod; 784 | 785 | internal LoggerExecutionWrapper(Logger logger, Func getIsDisabled = null) 786 | { 787 | _logger = logger; 788 | _callsiteLogger = new CallSiteExtension(); 789 | _getIsDisabled = getIsDisabled ?? (() => false); 790 | } 791 | 792 | internal Logger WrappedLogger 793 | { 794 | get { return _logger; } 795 | } 796 | 797 | [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] 798 | public bool Log(LogLevel logLevel, Func messageFunc, Exception exception = null, params object[] formatParameters) 799 | { 800 | if (_getIsDisabled()) 801 | { 802 | return false; 803 | } 804 | if (messageFunc == null) 805 | { 806 | return _logger(logLevel, null); 807 | } 808 | 809 | #if !LIBLOG_PORTABLE 810 | // Callsite HACK - Using the messageFunc to provide the callsite-logger-type 811 | var lastExtensionMethod = _lastExtensionMethod; 812 | if (lastExtensionMethod == null || !lastExtensionMethod.Equals(messageFunc)) 813 | { 814 | // Callsite HACK - Cache the last validated messageFunc as Equals is faster than type-check 815 | lastExtensionMethod = null; 816 | var methodType = messageFunc.Method.DeclaringType; 817 | if (methodType == typeof(LogExtensions) || (methodType != null && methodType.DeclaringType == typeof(LogExtensions))) 818 | { 819 | lastExtensionMethod = messageFunc; 820 | } 821 | } 822 | 823 | if (lastExtensionMethod != null) 824 | { 825 | // Callsite HACK - LogExtensions has called virtual ILog interface method to get here, callsite-stack is good 826 | _lastExtensionMethod = lastExtensionMethod; 827 | return _logger(logLevel, LogExtensions.WrapLogSafeInternal(this, messageFunc), exception, formatParameters); 828 | } 829 | else 830 | #endif 831 | { 832 | Func wrappedMessageFunc = () => 833 | { 834 | try 835 | { 836 | return messageFunc(); 837 | } 838 | catch (Exception ex) 839 | { 840 | _logger(LogLevel.Error, () => FailedToGenerateLogMessage, ex); 841 | } 842 | return null; 843 | }; 844 | 845 | // Callsite HACK - Need to ensure proper callsite stack without inlining, so calling the logger within a virtual interface method 846 | return _callsiteLogger.Log(_logger, logLevel, wrappedMessageFunc, exception, formatParameters); 847 | } 848 | } 849 | 850 | interface ICallSiteExtension 851 | { 852 | bool Log(Logger logger, LogLevel logLevel, Func messageFunc, Exception exception, object[] formatParameters); 853 | } 854 | 855 | class CallSiteExtension : ICallSiteExtension 856 | { 857 | bool ICallSiteExtension.Log(Logger logger, LogLevel logLevel, Func messageFunc, Exception exception, object[] formatParameters) 858 | { 859 | return logger(logLevel, messageFunc, exception, formatParameters); 860 | } 861 | } 862 | } 863 | #endif 864 | } 865 | 866 | #if LIBLOG_PROVIDERS_ONLY 867 | namespace YourRootNamespace.LibLog.LogProviders 868 | #else 869 | namespace SRTM.Logging.LogProviders 870 | #endif 871 | { 872 | using System; 873 | using System.Collections.Generic; 874 | using System.Diagnostics.CodeAnalysis; 875 | #if !LIBLOG_PORTABLE 876 | using System.Diagnostics; 877 | #endif 878 | using System.Globalization; 879 | using System.Linq; 880 | using System.Linq.Expressions; 881 | using System.Reflection; 882 | #if !LIBLOG_PORTABLE 883 | using System.Text; 884 | #endif 885 | using System.Text.RegularExpressions; 886 | 887 | #if !LIBLOG_PORTABLE 888 | [ExcludeFromCodeCoverage] 889 | #endif 890 | internal abstract class LogProviderBase : ILogProvider 891 | { 892 | protected delegate IDisposable OpenNdc(string message); 893 | protected delegate IDisposable OpenMdc(string key, object value, bool destructure); 894 | 895 | private readonly Lazy _lazyOpenNdcMethod; 896 | private readonly Lazy _lazyOpenMdcMethod; 897 | private static readonly IDisposable NoopDisposableInstance = new DisposableAction(); 898 | 899 | protected LogProviderBase() 900 | { 901 | _lazyOpenNdcMethod 902 | = new Lazy(GetOpenNdcMethod); 903 | _lazyOpenMdcMethod 904 | = new Lazy(GetOpenMdcMethod); 905 | } 906 | 907 | public abstract Logger GetLogger(string name); 908 | 909 | public IDisposable OpenNestedContext(string message) 910 | { 911 | return _lazyOpenNdcMethod.Value(message); 912 | } 913 | 914 | public IDisposable OpenMappedContext(string key, object value, bool destructure = false) 915 | { 916 | return _lazyOpenMdcMethod.Value(key, value, destructure); 917 | } 918 | 919 | protected virtual OpenNdc GetOpenNdcMethod() 920 | { 921 | return _ => NoopDisposableInstance; 922 | } 923 | 924 | protected virtual OpenMdc GetOpenMdcMethod() 925 | { 926 | return (_, __, ___) => NoopDisposableInstance; 927 | } 928 | } 929 | 930 | #if !LIBLOG_PORTABLE 931 | [ExcludeFromCodeCoverage] 932 | #endif 933 | internal class NLogLogProvider : LogProviderBase 934 | { 935 | private readonly Func _getLoggerByNameDelegate; 936 | private static bool s_providerIsAvailableOverride = true; 937 | 938 | [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "LogManager")] 939 | [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "NLog")] 940 | public NLogLogProvider() 941 | { 942 | if (!IsLoggerAvailable()) 943 | { 944 | throw new InvalidOperationException("NLog.LogManager not found"); 945 | } 946 | _getLoggerByNameDelegate = GetGetLoggerMethodCall(); 947 | } 948 | 949 | public static bool ProviderIsAvailableOverride 950 | { 951 | get { return s_providerIsAvailableOverride; } 952 | set { s_providerIsAvailableOverride = value; } 953 | } 954 | 955 | public override Logger GetLogger(string name) 956 | { 957 | return new NLogLogger(_getLoggerByNameDelegate(name)).Log; 958 | } 959 | 960 | public static bool IsLoggerAvailable() 961 | { 962 | return ProviderIsAvailableOverride && GetLogManagerType() != null; 963 | } 964 | 965 | protected override OpenNdc GetOpenNdcMethod() 966 | { 967 | Type ndcContextType = Type.GetType("NLog.NestedDiagnosticsContext, NLog"); 968 | MethodInfo pushMethod = ndcContextType.GetMethodPortable("Push", typeof(string)); 969 | ParameterExpression messageParam = Expression.Parameter(typeof(string), "message"); 970 | MethodCallExpression pushMethodCall = Expression.Call(null, pushMethod, messageParam); 971 | return Expression.Lambda(pushMethodCall, messageParam).Compile(); 972 | } 973 | 974 | protected override OpenMdc GetOpenMdcMethod() 975 | { 976 | Type mdcContextType = Type.GetType("NLog.MappedDiagnosticsContext, NLog"); 977 | 978 | MethodInfo setMethod = mdcContextType.GetMethodPortable("Set", typeof(string), typeof(string)); 979 | MethodInfo removeMethod = mdcContextType.GetMethodPortable("Remove", typeof(string)); 980 | ParameterExpression keyParam = Expression.Parameter(typeof(string), "key"); 981 | ParameterExpression valueParam = Expression.Parameter(typeof(string), "value"); 982 | 983 | MethodCallExpression setMethodCall = Expression.Call(null, setMethod, keyParam, valueParam); 984 | MethodCallExpression removeMethodCall = Expression.Call(null, removeMethod, keyParam); 985 | 986 | Action set = Expression 987 | .Lambda>(setMethodCall, keyParam, valueParam) 988 | .Compile(); 989 | Action remove = Expression 990 | .Lambda>(removeMethodCall, keyParam) 991 | .Compile(); 992 | 993 | return (key, value, _) => 994 | { 995 | set(key, value.ToString()); 996 | return new DisposableAction(() => remove(key)); 997 | }; 998 | } 999 | 1000 | private static Type GetLogManagerType() 1001 | { 1002 | return Type.GetType("NLog.LogManager, NLog"); 1003 | } 1004 | 1005 | private static Func GetGetLoggerMethodCall() 1006 | { 1007 | Type logManagerType = GetLogManagerType(); 1008 | MethodInfo method = logManagerType.GetMethodPortable("GetLogger", typeof(string)); 1009 | ParameterExpression nameParam = Expression.Parameter(typeof(string), "name"); 1010 | MethodCallExpression methodCall = Expression.Call(null, method, nameParam); 1011 | return Expression.Lambda>(methodCall, nameParam).Compile(); 1012 | } 1013 | 1014 | #if !LIBLOG_PORTABLE 1015 | [ExcludeFromCodeCoverage] 1016 | #endif 1017 | internal class NLogLogger 1018 | { 1019 | private readonly dynamic _logger; 1020 | 1021 | private static Func _logEventInfoFact; 1022 | 1023 | private static readonly object _levelTrace; 1024 | private static readonly object _levelDebug; 1025 | private static readonly object _levelInfo; 1026 | private static readonly object _levelWarn; 1027 | private static readonly object _levelError; 1028 | private static readonly object _levelFatal; 1029 | 1030 | static NLogLogger() 1031 | { 1032 | try 1033 | { 1034 | var logEventLevelType = Type.GetType("NLog.LogLevel, NLog"); 1035 | if (logEventLevelType == null) 1036 | { 1037 | throw new InvalidOperationException("Type NLog.LogLevel was not found."); 1038 | } 1039 | 1040 | var levelFields = logEventLevelType.GetFieldsPortable().ToList(); 1041 | _levelTrace = levelFields.First(x => x.Name == "Trace").GetValue(null); 1042 | _levelDebug = levelFields.First(x => x.Name == "Debug").GetValue(null); 1043 | _levelInfo = levelFields.First(x => x.Name == "Info").GetValue(null); 1044 | _levelWarn = levelFields.First(x => x.Name == "Warn").GetValue(null); 1045 | _levelError = levelFields.First(x => x.Name == "Error").GetValue(null); 1046 | _levelFatal = levelFields.First(x => x.Name == "Fatal").GetValue(null); 1047 | 1048 | var logEventInfoType = Type.GetType("NLog.LogEventInfo, NLog"); 1049 | if (logEventInfoType == null) 1050 | { 1051 | throw new InvalidOperationException("Type NLog.LogEventInfo was not found."); 1052 | } 1053 | 1054 | ConstructorInfo loggingEventConstructor = 1055 | logEventInfoType.GetConstructorPortable(logEventLevelType, typeof(string), typeof(IFormatProvider), typeof(string), typeof(object[]), typeof(Exception)); 1056 | 1057 | ParameterExpression loggerNameParam = Expression.Parameter(typeof(string)); 1058 | ParameterExpression levelParam = Expression.Parameter(typeof(object)); 1059 | ParameterExpression messageParam = Expression.Parameter(typeof(string)); 1060 | ParameterExpression exceptionParam = Expression.Parameter(typeof(Exception)); 1061 | UnaryExpression levelCast = Expression.Convert(levelParam, logEventLevelType); 1062 | 1063 | NewExpression newLoggingEventExpression = 1064 | Expression.New(loggingEventConstructor, 1065 | levelCast, 1066 | loggerNameParam, 1067 | Expression.Constant(null, typeof(IFormatProvider)), 1068 | messageParam, 1069 | Expression.Constant(null, typeof(object[])), 1070 | exceptionParam 1071 | ); 1072 | 1073 | _logEventInfoFact = Expression.Lambda>(newLoggingEventExpression, 1074 | loggerNameParam, levelParam, messageParam, exceptionParam).Compile(); 1075 | } 1076 | catch { } 1077 | } 1078 | 1079 | internal NLogLogger(dynamic logger) 1080 | { 1081 | _logger = logger; 1082 | } 1083 | 1084 | [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] 1085 | public bool Log(LogLevel logLevel, Func messageFunc, Exception exception, params object[] formatParameters) 1086 | { 1087 | if (messageFunc == null) 1088 | { 1089 | return IsLogLevelEnable(logLevel); 1090 | } 1091 | 1092 | var callsiteMessageFunc = messageFunc; 1093 | messageFunc = LogMessageFormatter.SimulateStructuredLogging(messageFunc, formatParameters); 1094 | 1095 | if (_logEventInfoFact != null) 1096 | { 1097 | if (IsLogLevelEnable(logLevel)) 1098 | { 1099 | Type callsiteLoggerType = typeof(NLogLogger); 1100 | #if !LIBLOG_PORTABLE 1101 | // Callsite HACK - Extract the callsite-logger-type from the messageFunc 1102 | var methodType = callsiteMessageFunc.Method.DeclaringType; 1103 | if (methodType == typeof(LogExtensions) || (methodType != null && methodType.DeclaringType == typeof(LogExtensions))) 1104 | { 1105 | callsiteLoggerType = typeof(LogExtensions); 1106 | } 1107 | else if (methodType == typeof(LoggerExecutionWrapper) || (methodType != null && methodType.DeclaringType == typeof(LoggerExecutionWrapper))) 1108 | { 1109 | callsiteLoggerType = typeof(LoggerExecutionWrapper); 1110 | } 1111 | #endif 1112 | var nlogLevel = this.TranslateLevel(logLevel); 1113 | var nlogEvent = _logEventInfoFact(_logger.Name, nlogLevel, messageFunc(), exception); 1114 | _logger.Log(callsiteLoggerType, nlogEvent); 1115 | return true; 1116 | } 1117 | return false; 1118 | } 1119 | 1120 | if (exception != null) 1121 | { 1122 | return LogException(logLevel, messageFunc, exception); 1123 | } 1124 | 1125 | switch (logLevel) 1126 | { 1127 | case LogLevel.Debug: 1128 | if (_logger.IsDebugEnabled) 1129 | { 1130 | _logger.Debug(messageFunc()); 1131 | return true; 1132 | } 1133 | break; 1134 | case LogLevel.Info: 1135 | if (_logger.IsInfoEnabled) 1136 | { 1137 | _logger.Info(messageFunc()); 1138 | return true; 1139 | } 1140 | break; 1141 | case LogLevel.Warn: 1142 | if (_logger.IsWarnEnabled) 1143 | { 1144 | _logger.Warn(messageFunc()); 1145 | return true; 1146 | } 1147 | break; 1148 | case LogLevel.Error: 1149 | if (_logger.IsErrorEnabled) 1150 | { 1151 | _logger.Error(messageFunc()); 1152 | return true; 1153 | } 1154 | break; 1155 | case LogLevel.Fatal: 1156 | if (_logger.IsFatalEnabled) 1157 | { 1158 | _logger.Fatal(messageFunc()); 1159 | return true; 1160 | } 1161 | break; 1162 | default: 1163 | if (_logger.IsTraceEnabled) 1164 | { 1165 | _logger.Trace(messageFunc()); 1166 | return true; 1167 | } 1168 | break; 1169 | } 1170 | return false; 1171 | } 1172 | 1173 | [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] 1174 | private bool LogException(LogLevel logLevel, Func messageFunc, Exception exception) 1175 | { 1176 | switch (logLevel) 1177 | { 1178 | case LogLevel.Debug: 1179 | if (_logger.IsDebugEnabled) 1180 | { 1181 | _logger.DebugException(messageFunc(), exception); 1182 | return true; 1183 | } 1184 | break; 1185 | case LogLevel.Info: 1186 | if (_logger.IsInfoEnabled) 1187 | { 1188 | _logger.InfoException(messageFunc(), exception); 1189 | return true; 1190 | } 1191 | break; 1192 | case LogLevel.Warn: 1193 | if (_logger.IsWarnEnabled) 1194 | { 1195 | _logger.WarnException(messageFunc(), exception); 1196 | return true; 1197 | } 1198 | break; 1199 | case LogLevel.Error: 1200 | if (_logger.IsErrorEnabled) 1201 | { 1202 | _logger.ErrorException(messageFunc(), exception); 1203 | return true; 1204 | } 1205 | break; 1206 | case LogLevel.Fatal: 1207 | if (_logger.IsFatalEnabled) 1208 | { 1209 | _logger.FatalException(messageFunc(), exception); 1210 | return true; 1211 | } 1212 | break; 1213 | default: 1214 | if (_logger.IsTraceEnabled) 1215 | { 1216 | _logger.TraceException(messageFunc(), exception); 1217 | return true; 1218 | } 1219 | break; 1220 | } 1221 | return false; 1222 | } 1223 | 1224 | private bool IsLogLevelEnable(LogLevel logLevel) 1225 | { 1226 | switch (logLevel) 1227 | { 1228 | case LogLevel.Debug: 1229 | return _logger.IsDebugEnabled; 1230 | case LogLevel.Info: 1231 | return _logger.IsInfoEnabled; 1232 | case LogLevel.Warn: 1233 | return _logger.IsWarnEnabled; 1234 | case LogLevel.Error: 1235 | return _logger.IsErrorEnabled; 1236 | case LogLevel.Fatal: 1237 | return _logger.IsFatalEnabled; 1238 | default: 1239 | return _logger.IsTraceEnabled; 1240 | } 1241 | } 1242 | 1243 | private object TranslateLevel(LogLevel logLevel) 1244 | { 1245 | switch (logLevel) 1246 | { 1247 | case LogLevel.Trace: 1248 | return _levelTrace; 1249 | case LogLevel.Debug: 1250 | return _levelDebug; 1251 | case LogLevel.Info: 1252 | return _levelInfo; 1253 | case LogLevel.Warn: 1254 | return _levelWarn; 1255 | case LogLevel.Error: 1256 | return _levelError; 1257 | case LogLevel.Fatal: 1258 | return _levelFatal; 1259 | default: 1260 | throw new ArgumentOutOfRangeException("logLevel", logLevel, null); 1261 | } 1262 | } 1263 | } 1264 | } 1265 | 1266 | #if !LIBLOG_PORTABLE 1267 | [ExcludeFromCodeCoverage] 1268 | #endif 1269 | internal class Log4NetLogProvider : LogProviderBase 1270 | { 1271 | private readonly Func _getLoggerByNameDelegate; 1272 | private static bool s_providerIsAvailableOverride = true; 1273 | 1274 | [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "LogManager")] 1275 | public Log4NetLogProvider() 1276 | { 1277 | if (!IsLoggerAvailable()) 1278 | { 1279 | throw new InvalidOperationException("log4net.LogManager not found"); 1280 | } 1281 | _getLoggerByNameDelegate = GetGetLoggerMethodCall(); 1282 | } 1283 | 1284 | public static bool ProviderIsAvailableOverride 1285 | { 1286 | get { return s_providerIsAvailableOverride; } 1287 | set { s_providerIsAvailableOverride = value; } 1288 | } 1289 | 1290 | public override Logger GetLogger(string name) 1291 | { 1292 | return new Log4NetLogger(_getLoggerByNameDelegate(name)).Log; 1293 | } 1294 | 1295 | internal static bool IsLoggerAvailable() 1296 | { 1297 | return ProviderIsAvailableOverride && GetLogManagerType() != null; 1298 | } 1299 | 1300 | protected override OpenNdc GetOpenNdcMethod() 1301 | { 1302 | Type logicalThreadContextType = Type.GetType("log4net.LogicalThreadContext, log4net"); 1303 | PropertyInfo stacksProperty = logicalThreadContextType.GetPropertyPortable("Stacks"); 1304 | Type logicalThreadContextStacksType = stacksProperty.PropertyType; 1305 | PropertyInfo stacksIndexerProperty = logicalThreadContextStacksType.GetPropertyPortable("Item"); 1306 | Type stackType = stacksIndexerProperty.PropertyType; 1307 | MethodInfo pushMethod = stackType.GetMethodPortable("Push"); 1308 | 1309 | ParameterExpression messageParameter = 1310 | Expression.Parameter(typeof(string), "message"); 1311 | 1312 | // message => LogicalThreadContext.Stacks.Item["NDC"].Push(message); 1313 | MethodCallExpression callPushBody = 1314 | Expression.Call( 1315 | Expression.Property(Expression.Property(null, stacksProperty), 1316 | stacksIndexerProperty, 1317 | Expression.Constant("NDC")), 1318 | pushMethod, 1319 | messageParameter); 1320 | 1321 | OpenNdc result = 1322 | Expression.Lambda(callPushBody, messageParameter) 1323 | .Compile(); 1324 | 1325 | return result; 1326 | } 1327 | 1328 | protected override OpenMdc GetOpenMdcMethod() 1329 | { 1330 | Type logicalThreadContextType = Type.GetType("log4net.LogicalThreadContext, log4net"); 1331 | PropertyInfo propertiesProperty = logicalThreadContextType.GetPropertyPortable("Properties"); 1332 | Type logicalThreadContextPropertiesType = propertiesProperty.PropertyType; 1333 | PropertyInfo propertiesIndexerProperty = logicalThreadContextPropertiesType.GetPropertyPortable("Item"); 1334 | 1335 | MethodInfo removeMethod = logicalThreadContextPropertiesType.GetMethodPortable("Remove"); 1336 | 1337 | ParameterExpression keyParam = Expression.Parameter(typeof(string), "key"); 1338 | ParameterExpression valueParam = Expression.Parameter(typeof(string), "value"); 1339 | 1340 | MemberExpression propertiesExpression = Expression.Property(null, propertiesProperty); 1341 | 1342 | // (key, value) => LogicalThreadContext.Properties.Item[key] = value; 1343 | BinaryExpression setProperties = Expression.Assign(Expression.Property(propertiesExpression, propertiesIndexerProperty, keyParam), valueParam); 1344 | 1345 | // key => LogicalThreadContext.Properties.Remove(key); 1346 | MethodCallExpression removeMethodCall = Expression.Call(propertiesExpression, removeMethod, keyParam); 1347 | 1348 | Action set = Expression 1349 | .Lambda>(setProperties, keyParam, valueParam) 1350 | .Compile(); 1351 | 1352 | Action remove = Expression 1353 | .Lambda>(removeMethodCall, keyParam) 1354 | .Compile(); 1355 | 1356 | return (key, value, _) => 1357 | { 1358 | set(key, value.ToString()); 1359 | return new DisposableAction(() => remove(key)); 1360 | }; 1361 | } 1362 | 1363 | private static Type GetLogManagerType() 1364 | { 1365 | return Type.GetType("log4net.LogManager, log4net"); 1366 | } 1367 | 1368 | private static Func GetGetLoggerMethodCall() 1369 | { 1370 | Type logManagerType = GetLogManagerType(); 1371 | MethodInfo method = logManagerType.GetMethodPortable("GetLogger", typeof(string)); 1372 | ParameterExpression nameParam = Expression.Parameter(typeof(string), "name"); 1373 | MethodCallExpression methodCall = Expression.Call(null, method, nameParam); 1374 | return Expression.Lambda>(methodCall, nameParam).Compile(); 1375 | } 1376 | 1377 | #if !LIBLOG_PORTABLE 1378 | [ExcludeFromCodeCoverage] 1379 | #endif 1380 | internal class Log4NetLogger 1381 | { 1382 | private readonly dynamic _logger; 1383 | private static Type s_callerStackBoundaryType; 1384 | private static readonly object CallerStackBoundaryTypeSync = new object(); 1385 | 1386 | private static readonly object _levelDebug; 1387 | private static readonly object _levelInfo; 1388 | private static readonly object _levelWarn; 1389 | private static readonly object _levelError; 1390 | private static readonly object _levelFatal; 1391 | private static readonly Func _isEnabledForDelegate; 1392 | private static readonly Action _logDelegate; 1393 | private static readonly Func _createLoggingEvent; 1394 | private static readonly Action _loggingEventPropertySetter; 1395 | 1396 | static Log4NetLogger() 1397 | { 1398 | var logEventLevelType = Type.GetType("log4net.Core.Level, log4net"); 1399 | if (logEventLevelType == null) 1400 | { 1401 | throw new InvalidOperationException("Type log4net.Core.Level was not found."); 1402 | } 1403 | 1404 | var levelFields = logEventLevelType.GetFieldsPortable().ToList(); 1405 | _levelDebug = levelFields.First(x => x.Name == "Debug").GetValue(null); 1406 | _levelInfo = levelFields.First(x => x.Name == "Info").GetValue(null); 1407 | _levelWarn = levelFields.First(x => x.Name == "Warn").GetValue(null); 1408 | _levelError = levelFields.First(x => x.Name == "Error").GetValue(null); 1409 | _levelFatal = levelFields.First(x => x.Name == "Fatal").GetValue(null); 1410 | 1411 | // Func isEnabledFor = (logger, level) => { return ((log4net.Core.ILogger)logger).IsEnabled(level); } 1412 | var loggerType = Type.GetType("log4net.Core.ILogger, log4net"); 1413 | if (loggerType == null) 1414 | { 1415 | throw new InvalidOperationException("Type log4net.Core.ILogger, was not found."); 1416 | } 1417 | ParameterExpression instanceParam = Expression.Parameter(typeof(object)); 1418 | UnaryExpression instanceCast = Expression.Convert(instanceParam, loggerType); 1419 | ParameterExpression levelParam = Expression.Parameter(typeof(object)); 1420 | UnaryExpression levelCast = Expression.Convert(levelParam, logEventLevelType); 1421 | _isEnabledForDelegate = GetIsEnabledFor(loggerType, logEventLevelType, instanceCast, levelCast, instanceParam, levelParam); 1422 | 1423 | Type loggingEventType = Type.GetType("log4net.Core.LoggingEvent, log4net"); 1424 | 1425 | _createLoggingEvent = GetCreateLoggingEvent(instanceParam, instanceCast, levelParam, levelCast, loggingEventType); 1426 | 1427 | _logDelegate = GetLogDelegate(loggerType, loggingEventType, instanceCast, instanceParam); 1428 | 1429 | _loggingEventPropertySetter = GetLoggingEventPropertySetter(loggingEventType); 1430 | } 1431 | 1432 | [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "ILogger")] 1433 | internal Log4NetLogger(dynamic logger) 1434 | { 1435 | _logger = logger.Logger; 1436 | } 1437 | 1438 | private static Action GetLogDelegate(Type loggerType, Type loggingEventType, UnaryExpression instanceCast, 1439 | ParameterExpression instanceParam) 1440 | { 1441 | //Action Log = 1442 | //(logger, callerStackBoundaryDeclaringType, level, message, exception) => { ((ILogger)logger).Log(new LoggingEvent(callerStackBoundaryDeclaringType, logger.Repository, logger.Name, level, message, exception)); } 1443 | MethodInfo writeExceptionMethodInfo = loggerType.GetMethodPortable("Log", 1444 | loggingEventType); 1445 | 1446 | ParameterExpression loggingEventParameter = 1447 | Expression.Parameter(typeof(object), "loggingEvent"); 1448 | 1449 | UnaryExpression loggingEventCasted = 1450 | Expression.Convert(loggingEventParameter, loggingEventType); 1451 | 1452 | var writeMethodExp = Expression.Call( 1453 | instanceCast, 1454 | writeExceptionMethodInfo, 1455 | loggingEventCasted); 1456 | 1457 | var logDelegate = Expression.Lambda>( 1458 | writeMethodExp, 1459 | instanceParam, 1460 | loggingEventParameter).Compile(); 1461 | 1462 | return logDelegate; 1463 | } 1464 | 1465 | private static Func GetCreateLoggingEvent(ParameterExpression instanceParam, UnaryExpression instanceCast, ParameterExpression levelParam, UnaryExpression levelCast, Type loggingEventType) 1466 | { 1467 | ParameterExpression callerStackBoundaryDeclaringTypeParam = Expression.Parameter(typeof(Type)); 1468 | ParameterExpression messageParam = Expression.Parameter(typeof(string)); 1469 | ParameterExpression exceptionParam = Expression.Parameter(typeof(Exception)); 1470 | 1471 | PropertyInfo repositoryProperty = loggingEventType.GetPropertyPortable("Repository"); 1472 | PropertyInfo levelProperty = loggingEventType.GetPropertyPortable("Level"); 1473 | 1474 | ConstructorInfo loggingEventConstructor = 1475 | loggingEventType.GetConstructorPortable(typeof(Type), repositoryProperty.PropertyType, typeof(string), levelProperty.PropertyType, typeof(object), typeof(Exception)); 1476 | 1477 | //Func Log = 1478 | //(logger, callerStackBoundaryDeclaringType, level, message, exception) => new LoggingEvent(callerStackBoundaryDeclaringType, ((ILogger)logger).Repository, ((ILogger)logger).Name, (Level)level, message, exception); } 1479 | NewExpression newLoggingEventExpression = 1480 | Expression.New(loggingEventConstructor, 1481 | callerStackBoundaryDeclaringTypeParam, 1482 | Expression.Property(instanceCast, "Repository"), 1483 | Expression.Property(instanceCast, "Name"), 1484 | levelCast, 1485 | messageParam, 1486 | exceptionParam); 1487 | 1488 | var createLoggingEvent = 1489 | Expression.Lambda>( 1490 | newLoggingEventExpression, 1491 | instanceParam, 1492 | callerStackBoundaryDeclaringTypeParam, 1493 | levelParam, 1494 | messageParam, 1495 | exceptionParam) 1496 | .Compile(); 1497 | 1498 | return createLoggingEvent; 1499 | } 1500 | 1501 | private static Func GetIsEnabledFor(Type loggerType, Type logEventLevelType, 1502 | UnaryExpression instanceCast, 1503 | UnaryExpression levelCast, 1504 | ParameterExpression instanceParam, 1505 | ParameterExpression levelParam) 1506 | { 1507 | MethodInfo isEnabledMethodInfo = loggerType.GetMethodPortable("IsEnabledFor", logEventLevelType); 1508 | MethodCallExpression isEnabledMethodCall = Expression.Call(instanceCast, isEnabledMethodInfo, levelCast); 1509 | 1510 | Func result = 1511 | Expression.Lambda>(isEnabledMethodCall, instanceParam, levelParam) 1512 | .Compile(); 1513 | 1514 | return result; 1515 | } 1516 | 1517 | private static Action GetLoggingEventPropertySetter(Type loggingEventType) 1518 | { 1519 | ParameterExpression loggingEventParameter = Expression.Parameter(typeof(object), "loggingEvent"); 1520 | ParameterExpression keyParameter = Expression.Parameter(typeof(string), "key"); 1521 | ParameterExpression valueParameter = Expression.Parameter(typeof(object), "value"); 1522 | 1523 | PropertyInfo propertiesProperty = loggingEventType.GetPropertyPortable("Properties"); 1524 | PropertyInfo item = propertiesProperty.PropertyType.GetPropertyPortable("Item"); 1525 | 1526 | // ((LoggingEvent)loggingEvent).Properties[key] = value; 1527 | var body = 1528 | Expression.Assign( 1529 | Expression.Property( 1530 | Expression.Property(Expression.Convert(loggingEventParameter, loggingEventType), 1531 | propertiesProperty), item, keyParameter), valueParameter); 1532 | 1533 | Action result = 1534 | Expression.Lambda> 1535 | (body, loggingEventParameter, keyParameter, 1536 | valueParameter) 1537 | .Compile(); 1538 | 1539 | return result; 1540 | } 1541 | 1542 | public bool Log(LogLevel logLevel, Func messageFunc, Exception exception, params object[] formatParameters) 1543 | { 1544 | if (messageFunc == null) 1545 | { 1546 | return IsLogLevelEnable(logLevel); 1547 | } 1548 | 1549 | if (!IsLogLevelEnable(logLevel)) 1550 | { 1551 | return false; 1552 | } 1553 | 1554 | string message = messageFunc(); 1555 | 1556 | IEnumerable patternMatches; 1557 | 1558 | string formattedMessage = 1559 | LogMessageFormatter.FormatStructuredMessage(message, 1560 | formatParameters, 1561 | out patternMatches); 1562 | 1563 | // determine correct caller - this might change due to jit optimizations with method inlining 1564 | if (s_callerStackBoundaryType == null) 1565 | { 1566 | lock (CallerStackBoundaryTypeSync) 1567 | { 1568 | #if !LIBLOG_PORTABLE 1569 | StackTrace stack = new StackTrace(); 1570 | Type thisType = GetType(); 1571 | s_callerStackBoundaryType = Type.GetType("LoggerExecutionWrapper"); 1572 | for (var i = 1; i < stack.FrameCount; i++) 1573 | { 1574 | if (!IsInTypeHierarchy(thisType, stack.GetFrame(i).GetMethod().DeclaringType)) 1575 | { 1576 | s_callerStackBoundaryType = stack.GetFrame(i - 1).GetMethod().DeclaringType; 1577 | break; 1578 | } 1579 | } 1580 | #else 1581 | s_callerStackBoundaryType = typeof (LoggerExecutionWrapper); 1582 | #endif 1583 | } 1584 | } 1585 | 1586 | var translatedLevel = TranslateLevel(logLevel); 1587 | 1588 | object loggingEvent = _createLoggingEvent(_logger, s_callerStackBoundaryType, translatedLevel, formattedMessage, exception); 1589 | 1590 | PopulateProperties(loggingEvent, patternMatches, formatParameters); 1591 | 1592 | _logDelegate(_logger, loggingEvent); 1593 | 1594 | return true; 1595 | } 1596 | 1597 | private void PopulateProperties(object loggingEvent, IEnumerable patternMatches, object[] formatParameters) 1598 | { 1599 | IEnumerable> keyToValue = 1600 | patternMatches.Zip(formatParameters, 1601 | (key, value) => new KeyValuePair(key, value)); 1602 | 1603 | foreach (KeyValuePair keyValuePair in keyToValue) 1604 | { 1605 | _loggingEventPropertySetter(loggingEvent, keyValuePair.Key, keyValuePair.Value); 1606 | } 1607 | } 1608 | 1609 | private static bool IsInTypeHierarchy(Type currentType, Type checkType) 1610 | { 1611 | while (currentType != null && currentType != typeof(object)) 1612 | { 1613 | if (currentType == checkType) 1614 | { 1615 | return true; 1616 | } 1617 | currentType = currentType.GetBaseTypePortable(); 1618 | } 1619 | return false; 1620 | } 1621 | 1622 | private bool IsLogLevelEnable(LogLevel logLevel) 1623 | { 1624 | var level = TranslateLevel(logLevel); 1625 | return _isEnabledForDelegate(_logger, level); 1626 | } 1627 | 1628 | private object TranslateLevel(LogLevel logLevel) 1629 | { 1630 | switch (logLevel) 1631 | { 1632 | case LogLevel.Trace: 1633 | case LogLevel.Debug: 1634 | return _levelDebug; 1635 | case LogLevel.Info: 1636 | return _levelInfo; 1637 | case LogLevel.Warn: 1638 | return _levelWarn; 1639 | case LogLevel.Error: 1640 | return _levelError; 1641 | case LogLevel.Fatal: 1642 | return _levelFatal; 1643 | default: 1644 | throw new ArgumentOutOfRangeException("logLevel", logLevel, null); 1645 | } 1646 | } 1647 | } 1648 | } 1649 | 1650 | #if !LIBLOG_PORTABLE 1651 | [ExcludeFromCodeCoverage] 1652 | #endif 1653 | internal class EntLibLogProvider : LogProviderBase 1654 | { 1655 | private const string TypeTemplate = "Microsoft.Practices.EnterpriseLibrary.Logging.{0}, Microsoft.Practices.EnterpriseLibrary.Logging"; 1656 | private static bool s_providerIsAvailableOverride = true; 1657 | private static readonly Type LogEntryType; 1658 | private static readonly Type LoggerType; 1659 | private static readonly Type TraceEventTypeType; 1660 | private static readonly Action WriteLogEntry; 1661 | private static readonly Func ShouldLogEntry; 1662 | 1663 | [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] 1664 | static EntLibLogProvider() 1665 | { 1666 | LogEntryType = Type.GetType(string.Format(CultureInfo.InvariantCulture, TypeTemplate, "LogEntry")); 1667 | LoggerType = Type.GetType(string.Format(CultureInfo.InvariantCulture, TypeTemplate, "Logger")); 1668 | TraceEventTypeType = TraceEventTypeValues.Type; 1669 | if (LogEntryType == null 1670 | || TraceEventTypeType == null 1671 | || LoggerType == null) 1672 | { 1673 | return; 1674 | } 1675 | WriteLogEntry = GetWriteLogEntry(); 1676 | ShouldLogEntry = GetShouldLogEntry(); 1677 | } 1678 | 1679 | [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "EnterpriseLibrary")] 1680 | public EntLibLogProvider() 1681 | { 1682 | if (!IsLoggerAvailable()) 1683 | { 1684 | throw new InvalidOperationException("Microsoft.Practices.EnterpriseLibrary.Logging.Logger not found"); 1685 | } 1686 | } 1687 | 1688 | public static bool ProviderIsAvailableOverride 1689 | { 1690 | get { return s_providerIsAvailableOverride; } 1691 | set { s_providerIsAvailableOverride = value; } 1692 | } 1693 | 1694 | public override Logger GetLogger(string name) 1695 | { 1696 | return new EntLibLogger(name, WriteLogEntry, ShouldLogEntry).Log; 1697 | } 1698 | 1699 | internal static bool IsLoggerAvailable() 1700 | { 1701 | return ProviderIsAvailableOverride 1702 | && TraceEventTypeType != null 1703 | && LogEntryType != null; 1704 | } 1705 | 1706 | private static Action GetWriteLogEntry() 1707 | { 1708 | // new LogEntry(...) 1709 | var logNameParameter = Expression.Parameter(typeof(string), "logName"); 1710 | var messageParameter = Expression.Parameter(typeof(string), "message"); 1711 | var severityParameter = Expression.Parameter(typeof(int), "severity"); 1712 | 1713 | MemberInitExpression memberInit = GetWriteLogExpression( 1714 | messageParameter, 1715 | Expression.Convert(severityParameter, TraceEventTypeType), 1716 | logNameParameter); 1717 | 1718 | //Logger.Write(new LogEntry(....)); 1719 | MethodInfo writeLogEntryMethod = LoggerType.GetMethodPortable("Write", LogEntryType); 1720 | var writeLogEntryExpression = Expression.Call(writeLogEntryMethod, memberInit); 1721 | 1722 | return Expression.Lambda>( 1723 | writeLogEntryExpression, 1724 | logNameParameter, 1725 | messageParameter, 1726 | severityParameter).Compile(); 1727 | } 1728 | 1729 | private static Func GetShouldLogEntry() 1730 | { 1731 | // new LogEntry(...) 1732 | var logNameParameter = Expression.Parameter(typeof(string), "logName"); 1733 | var severityParameter = Expression.Parameter(typeof(int), "severity"); 1734 | 1735 | MemberInitExpression memberInit = GetWriteLogExpression( 1736 | Expression.Constant("***dummy***"), 1737 | Expression.Convert(severityParameter, TraceEventTypeType), 1738 | logNameParameter); 1739 | 1740 | //Logger.Write(new LogEntry(....)); 1741 | MethodInfo writeLogEntryMethod = LoggerType.GetMethodPortable("ShouldLog", LogEntryType); 1742 | var writeLogEntryExpression = Expression.Call(writeLogEntryMethod, memberInit); 1743 | 1744 | return Expression.Lambda>( 1745 | writeLogEntryExpression, 1746 | logNameParameter, 1747 | severityParameter).Compile(); 1748 | } 1749 | 1750 | private static MemberInitExpression GetWriteLogExpression(Expression message, 1751 | Expression severityParameter, ParameterExpression logNameParameter) 1752 | { 1753 | var entryType = LogEntryType; 1754 | MemberInitExpression memberInit = Expression.MemberInit(Expression.New(entryType), 1755 | Expression.Bind(entryType.GetPropertyPortable("Message"), message), 1756 | Expression.Bind(entryType.GetPropertyPortable("Severity"), severityParameter), 1757 | Expression.Bind( 1758 | entryType.GetPropertyPortable("TimeStamp"), 1759 | Expression.Property(null, typeof(DateTime).GetPropertyPortable("UtcNow"))), 1760 | Expression.Bind( 1761 | entryType.GetPropertyPortable("Categories"), 1762 | Expression.ListInit( 1763 | Expression.New(typeof(List)), 1764 | typeof(List).GetMethodPortable("Add", typeof(string)), 1765 | logNameParameter))); 1766 | return memberInit; 1767 | } 1768 | 1769 | #if !LIBLOG_PORTABLE 1770 | [ExcludeFromCodeCoverage] 1771 | #endif 1772 | internal class EntLibLogger 1773 | { 1774 | private readonly string _loggerName; 1775 | private readonly Action _writeLog; 1776 | private readonly Func _shouldLog; 1777 | 1778 | internal EntLibLogger(string loggerName, Action writeLog, Func shouldLog) 1779 | { 1780 | _loggerName = loggerName; 1781 | _writeLog = writeLog; 1782 | _shouldLog = shouldLog; 1783 | } 1784 | 1785 | public bool Log(LogLevel logLevel, Func messageFunc, Exception exception, params object[] formatParameters) 1786 | { 1787 | var severity = MapSeverity(logLevel); 1788 | if (messageFunc == null) 1789 | { 1790 | return _shouldLog(_loggerName, severity); 1791 | } 1792 | 1793 | 1794 | messageFunc = LogMessageFormatter.SimulateStructuredLogging(messageFunc, formatParameters); 1795 | if (exception != null) 1796 | { 1797 | return LogException(logLevel, messageFunc, exception); 1798 | } 1799 | _writeLog(_loggerName, messageFunc(), severity); 1800 | return true; 1801 | } 1802 | 1803 | public bool LogException(LogLevel logLevel, Func messageFunc, Exception exception) 1804 | { 1805 | var severity = MapSeverity(logLevel); 1806 | var message = messageFunc() + Environment.NewLine + exception; 1807 | _writeLog(_loggerName, message, severity); 1808 | return true; 1809 | } 1810 | 1811 | private static int MapSeverity(LogLevel logLevel) 1812 | { 1813 | switch (logLevel) 1814 | { 1815 | case LogLevel.Fatal: 1816 | return TraceEventTypeValues.Critical; 1817 | case LogLevel.Error: 1818 | return TraceEventTypeValues.Error; 1819 | case LogLevel.Warn: 1820 | return TraceEventTypeValues.Warning; 1821 | case LogLevel.Info: 1822 | return TraceEventTypeValues.Information; 1823 | default: 1824 | return TraceEventTypeValues.Verbose; 1825 | } 1826 | } 1827 | } 1828 | } 1829 | 1830 | #if !LIBLOG_PORTABLE 1831 | [ExcludeFromCodeCoverage] 1832 | #endif 1833 | internal class SerilogLogProvider : LogProviderBase 1834 | { 1835 | private readonly Func _getLoggerByNameDelegate; 1836 | private static bool s_providerIsAvailableOverride = true; 1837 | private static Func _pushProperty; 1838 | 1839 | [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "Serilog")] 1840 | public SerilogLogProvider() 1841 | { 1842 | if (!IsLoggerAvailable()) 1843 | { 1844 | throw new InvalidOperationException("Serilog.Log not found"); 1845 | } 1846 | _getLoggerByNameDelegate = GetForContextMethodCall(); 1847 | _pushProperty = GetPushProperty(); 1848 | } 1849 | 1850 | public static bool ProviderIsAvailableOverride 1851 | { 1852 | get { return s_providerIsAvailableOverride; } 1853 | set { s_providerIsAvailableOverride = value; } 1854 | } 1855 | 1856 | public override Logger GetLogger(string name) 1857 | { 1858 | return new SerilogLogger(_getLoggerByNameDelegate(name)).Log; 1859 | } 1860 | 1861 | internal static bool IsLoggerAvailable() 1862 | { 1863 | return ProviderIsAvailableOverride && GetLogManagerType() != null; 1864 | } 1865 | 1866 | protected override OpenNdc GetOpenNdcMethod() 1867 | { 1868 | return message => _pushProperty("NDC", message, false); 1869 | } 1870 | 1871 | protected override OpenMdc GetOpenMdcMethod() 1872 | { 1873 | return (key, value, destructure) => _pushProperty(key, value, destructure); 1874 | } 1875 | 1876 | private static Func GetPushProperty() 1877 | { 1878 | Type ndcContextType = Type.GetType("Serilog.Context.LogContext, Serilog") ?? 1879 | Type.GetType("Serilog.Context.LogContext, Serilog.FullNetFx"); 1880 | 1881 | MethodInfo pushPropertyMethod = ndcContextType.GetMethodPortable( 1882 | "PushProperty", 1883 | typeof(string), 1884 | typeof(object), 1885 | typeof(bool)); 1886 | 1887 | ParameterExpression nameParam = Expression.Parameter(typeof(string), "name"); 1888 | ParameterExpression valueParam = Expression.Parameter(typeof(object), "value"); 1889 | ParameterExpression destructureObjectParam = Expression.Parameter(typeof(bool), "destructureObjects"); 1890 | MethodCallExpression pushPropertyMethodCall = Expression 1891 | .Call(null, pushPropertyMethod, nameParam, valueParam, destructureObjectParam); 1892 | var pushProperty = Expression 1893 | .Lambda>( 1894 | pushPropertyMethodCall, 1895 | nameParam, 1896 | valueParam, 1897 | destructureObjectParam) 1898 | .Compile(); 1899 | 1900 | return (key, value, destructure) => pushProperty(key, value, destructure); 1901 | } 1902 | 1903 | private static Type GetLogManagerType() 1904 | { 1905 | return Type.GetType("Serilog.Log, Serilog"); 1906 | } 1907 | 1908 | private static Func GetForContextMethodCall() 1909 | { 1910 | Type logManagerType = GetLogManagerType(); 1911 | MethodInfo method = logManagerType.GetMethodPortable("ForContext", typeof(string), typeof(object), typeof(bool)); 1912 | ParameterExpression propertyNameParam = Expression.Parameter(typeof(string), "propertyName"); 1913 | ParameterExpression valueParam = Expression.Parameter(typeof(object), "value"); 1914 | ParameterExpression destructureObjectsParam = Expression.Parameter(typeof(bool), "destructureObjects"); 1915 | MethodCallExpression methodCall = Expression.Call(null, method, new Expression[] 1916 | { 1917 | propertyNameParam, 1918 | valueParam, 1919 | destructureObjectsParam 1920 | }); 1921 | var func = Expression.Lambda>( 1922 | methodCall, 1923 | propertyNameParam, 1924 | valueParam, 1925 | destructureObjectsParam) 1926 | .Compile(); 1927 | return name => func("SourceContext", name, false); 1928 | } 1929 | 1930 | #if !LIBLOG_PORTABLE 1931 | [ExcludeFromCodeCoverage] 1932 | #endif 1933 | internal class SerilogLogger 1934 | { 1935 | private readonly object _logger; 1936 | private static readonly object DebugLevel; 1937 | private static readonly object ErrorLevel; 1938 | private static readonly object FatalLevel; 1939 | private static readonly object InformationLevel; 1940 | private static readonly object VerboseLevel; 1941 | private static readonly object WarningLevel; 1942 | private static readonly Func IsEnabled; 1943 | private static readonly Action Write; 1944 | private static readonly Action WriteException; 1945 | 1946 | [SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] 1947 | [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] 1948 | [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "ILogger")] 1949 | [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "LogEventLevel")] 1950 | [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "Serilog")] 1951 | static SerilogLogger() 1952 | { 1953 | var logEventLevelType = Type.GetType("Serilog.Events.LogEventLevel, Serilog"); 1954 | if (logEventLevelType == null) 1955 | { 1956 | throw new InvalidOperationException("Type Serilog.Events.LogEventLevel was not found."); 1957 | } 1958 | DebugLevel = Enum.Parse(logEventLevelType, "Debug", false); 1959 | ErrorLevel = Enum.Parse(logEventLevelType, "Error", false); 1960 | FatalLevel = Enum.Parse(logEventLevelType, "Fatal", false); 1961 | InformationLevel = Enum.Parse(logEventLevelType, "Information", false); 1962 | VerboseLevel = Enum.Parse(logEventLevelType, "Verbose", false); 1963 | WarningLevel = Enum.Parse(logEventLevelType, "Warning", false); 1964 | 1965 | // Func isEnabled = (logger, level) => { return ((SeriLog.ILogger)logger).IsEnabled(level); } 1966 | var loggerType = Type.GetType("Serilog.ILogger, Serilog"); 1967 | if (loggerType == null) 1968 | { 1969 | throw new InvalidOperationException("Type Serilog.ILogger was not found."); 1970 | } 1971 | MethodInfo isEnabledMethodInfo = loggerType.GetMethodPortable("IsEnabled", logEventLevelType); 1972 | ParameterExpression instanceParam = Expression.Parameter(typeof(object)); 1973 | UnaryExpression instanceCast = Expression.Convert(instanceParam, loggerType); 1974 | ParameterExpression levelParam = Expression.Parameter(typeof(object)); 1975 | UnaryExpression levelCast = Expression.Convert(levelParam, logEventLevelType); 1976 | MethodCallExpression isEnabledMethodCall = Expression.Call(instanceCast, isEnabledMethodInfo, levelCast); 1977 | IsEnabled = Expression.Lambda>(isEnabledMethodCall, instanceParam, levelParam).Compile(); 1978 | 1979 | // Action Write = 1980 | // (logger, level, message, params) => { ((SeriLog.ILoggerILogger)logger).Write(level, message, params); } 1981 | MethodInfo writeMethodInfo = loggerType.GetMethodPortable("Write", logEventLevelType, typeof(string), typeof(object[])); 1982 | ParameterExpression messageParam = Expression.Parameter(typeof(string)); 1983 | ParameterExpression propertyValuesParam = Expression.Parameter(typeof(object[])); 1984 | MethodCallExpression writeMethodExp = Expression.Call( 1985 | instanceCast, 1986 | writeMethodInfo, 1987 | levelCast, 1988 | messageParam, 1989 | propertyValuesParam); 1990 | var expression = Expression.Lambda>( 1991 | writeMethodExp, 1992 | instanceParam, 1993 | levelParam, 1994 | messageParam, 1995 | propertyValuesParam); 1996 | Write = expression.Compile(); 1997 | 1998 | // Action WriteException = 1999 | // (logger, level, exception, message) => { ((ILogger)logger).Write(level, exception, message, new object[]); } 2000 | MethodInfo writeExceptionMethodInfo = loggerType.GetMethodPortable("Write", 2001 | logEventLevelType, 2002 | typeof(Exception), 2003 | typeof(string), 2004 | typeof(object[])); 2005 | ParameterExpression exceptionParam = Expression.Parameter(typeof(Exception)); 2006 | writeMethodExp = Expression.Call( 2007 | instanceCast, 2008 | writeExceptionMethodInfo, 2009 | levelCast, 2010 | exceptionParam, 2011 | messageParam, 2012 | propertyValuesParam); 2013 | WriteException = Expression.Lambda>( 2014 | writeMethodExp, 2015 | instanceParam, 2016 | levelParam, 2017 | exceptionParam, 2018 | messageParam, 2019 | propertyValuesParam).Compile(); 2020 | } 2021 | 2022 | internal SerilogLogger(object logger) 2023 | { 2024 | _logger = logger; 2025 | } 2026 | 2027 | public bool Log(LogLevel logLevel, Func messageFunc, Exception exception, params object[] formatParameters) 2028 | { 2029 | var translatedLevel = TranslateLevel(logLevel); 2030 | if (messageFunc == null) 2031 | { 2032 | return IsEnabled(_logger, translatedLevel); 2033 | } 2034 | 2035 | if (!IsEnabled(_logger, translatedLevel)) 2036 | { 2037 | return false; 2038 | } 2039 | 2040 | if (exception != null) 2041 | { 2042 | LogException(translatedLevel, messageFunc, exception, formatParameters); 2043 | } 2044 | else 2045 | { 2046 | LogMessage(translatedLevel, messageFunc, formatParameters); 2047 | } 2048 | 2049 | return true; 2050 | } 2051 | 2052 | private void LogMessage(object translatedLevel, Func messageFunc, object[] formatParameters) 2053 | { 2054 | Write(_logger, translatedLevel, messageFunc(), formatParameters); 2055 | } 2056 | 2057 | private void LogException(object logLevel, Func messageFunc, Exception exception, object[] formatParams) 2058 | { 2059 | WriteException(_logger, logLevel, exception, messageFunc(), formatParams); 2060 | } 2061 | 2062 | private static object TranslateLevel(LogLevel logLevel) 2063 | { 2064 | switch (logLevel) 2065 | { 2066 | case LogLevel.Fatal: 2067 | return FatalLevel; 2068 | case LogLevel.Error: 2069 | return ErrorLevel; 2070 | case LogLevel.Warn: 2071 | return WarningLevel; 2072 | case LogLevel.Info: 2073 | return InformationLevel; 2074 | case LogLevel.Trace: 2075 | return VerboseLevel; 2076 | default: 2077 | return DebugLevel; 2078 | } 2079 | } 2080 | } 2081 | } 2082 | 2083 | #if !LIBLOG_PORTABLE 2084 | [ExcludeFromCodeCoverage] 2085 | #endif 2086 | internal class LoupeLogProvider : LogProviderBase 2087 | { 2088 | /// 2089 | /// The form of the Loupe Log.Write method we're using 2090 | /// 2091 | internal delegate void WriteDelegate( 2092 | int severity, 2093 | string logSystem, 2094 | int skipFrames, 2095 | Exception exception, 2096 | bool attributeToException, 2097 | int writeMode, 2098 | string detailsXml, 2099 | string category, 2100 | string caption, 2101 | string description, 2102 | params object[] args 2103 | ); 2104 | 2105 | private static bool s_providerIsAvailableOverride = true; 2106 | private readonly WriteDelegate _logWriteDelegate; 2107 | 2108 | public LoupeLogProvider() 2109 | { 2110 | if (!IsLoggerAvailable()) 2111 | { 2112 | throw new InvalidOperationException("Gibraltar.Agent.Log (Loupe) not found"); 2113 | } 2114 | 2115 | _logWriteDelegate = GetLogWriteDelegate(); 2116 | } 2117 | 2118 | /// 2119 | /// Gets or sets a value indicating whether [provider is available override]. Used in tests. 2120 | /// 2121 | /// 2122 | /// true if [provider is available override]; otherwise, false. 2123 | /// 2124 | public static bool ProviderIsAvailableOverride 2125 | { 2126 | get { return s_providerIsAvailableOverride; } 2127 | set { s_providerIsAvailableOverride = value; } 2128 | } 2129 | 2130 | public override Logger GetLogger(string name) 2131 | { 2132 | return new LoupeLogger(name, _logWriteDelegate).Log; 2133 | } 2134 | 2135 | public static bool IsLoggerAvailable() 2136 | { 2137 | return ProviderIsAvailableOverride && GetLogManagerType() != null; 2138 | } 2139 | 2140 | private static Type GetLogManagerType() 2141 | { 2142 | return Type.GetType("Gibraltar.Agent.Log, Gibraltar.Agent"); 2143 | } 2144 | 2145 | private static WriteDelegate GetLogWriteDelegate() 2146 | { 2147 | Type logManagerType = GetLogManagerType(); 2148 | Type logMessageSeverityType = Type.GetType("Gibraltar.Agent.LogMessageSeverity, Gibraltar.Agent"); 2149 | Type logWriteModeType = Type.GetType("Gibraltar.Agent.LogWriteMode, Gibraltar.Agent"); 2150 | 2151 | MethodInfo method = logManagerType.GetMethodPortable( 2152 | "Write", 2153 | logMessageSeverityType, typeof(string), typeof(int), typeof(Exception), typeof(bool), 2154 | logWriteModeType, typeof(string), typeof(string), typeof(string), typeof(string), typeof(object[])); 2155 | 2156 | var callDelegate = (WriteDelegate)method.CreateDelegate(typeof(WriteDelegate)); 2157 | return callDelegate; 2158 | } 2159 | 2160 | #if !LIBLOG_PORTABLE 2161 | [ExcludeFromCodeCoverage] 2162 | #endif 2163 | internal class LoupeLogger 2164 | { 2165 | private const string LogSystem = "LibLog"; 2166 | 2167 | private readonly string _category; 2168 | private readonly WriteDelegate _logWriteDelegate; 2169 | private readonly int _skipLevel; 2170 | 2171 | internal LoupeLogger(string category, WriteDelegate logWriteDelegate) 2172 | { 2173 | _category = category; 2174 | _logWriteDelegate = logWriteDelegate; 2175 | #if DEBUG 2176 | _skipLevel = 2; 2177 | #else 2178 | _skipLevel = 1; 2179 | #endif 2180 | } 2181 | 2182 | public bool Log(LogLevel logLevel, Func messageFunc, Exception exception, params object[] formatParameters) 2183 | { 2184 | if (messageFunc == null) 2185 | { 2186 | //nothing to log.. 2187 | return true; 2188 | } 2189 | 2190 | messageFunc = LogMessageFormatter.SimulateStructuredLogging(messageFunc, formatParameters); 2191 | 2192 | _logWriteDelegate(ToLogMessageSeverity(logLevel), LogSystem, _skipLevel, exception, true, 0, null, 2193 | _category, null, messageFunc.Invoke()); 2194 | 2195 | return true; 2196 | } 2197 | 2198 | private static int ToLogMessageSeverity(LogLevel logLevel) 2199 | { 2200 | switch (logLevel) 2201 | { 2202 | case LogLevel.Trace: 2203 | return TraceEventTypeValues.Verbose; 2204 | case LogLevel.Debug: 2205 | return TraceEventTypeValues.Verbose; 2206 | case LogLevel.Info: 2207 | return TraceEventTypeValues.Information; 2208 | case LogLevel.Warn: 2209 | return TraceEventTypeValues.Warning; 2210 | case LogLevel.Error: 2211 | return TraceEventTypeValues.Error; 2212 | case LogLevel.Fatal: 2213 | return TraceEventTypeValues.Critical; 2214 | default: 2215 | throw new ArgumentOutOfRangeException("logLevel"); 2216 | } 2217 | } 2218 | } 2219 | } 2220 | 2221 | #if !LIBLOG_PORTABLE 2222 | [ExcludeFromCodeCoverage] 2223 | #endif 2224 | internal static class TraceEventTypeValues 2225 | { 2226 | internal static readonly Type Type; 2227 | internal static readonly int Verbose; 2228 | internal static readonly int Information; 2229 | internal static readonly int Warning; 2230 | internal static readonly int Error; 2231 | internal static readonly int Critical; 2232 | 2233 | [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] 2234 | static TraceEventTypeValues() 2235 | { 2236 | var assembly = typeof(Uri).GetAssemblyPortable(); // This is to get to the System.dll assembly in a PCL compatible way. 2237 | if (assembly == null) 2238 | { 2239 | return; 2240 | } 2241 | Type = assembly.GetType("System.Diagnostics.TraceEventType"); 2242 | if (Type == null) return; 2243 | Verbose = (int)Enum.Parse(Type, "Verbose", false); 2244 | Information = (int)Enum.Parse(Type, "Information", false); 2245 | Warning = (int)Enum.Parse(Type, "Warning", false); 2246 | Error = (int)Enum.Parse(Type, "Error", false); 2247 | Critical = (int)Enum.Parse(Type, "Critical", false); 2248 | } 2249 | } 2250 | 2251 | #if !LIBLOG_PORTABLE 2252 | [ExcludeFromCodeCoverage] 2253 | #endif 2254 | internal static class LogMessageFormatter 2255 | { 2256 | //private static readonly Regex Pattern = new Regex(@"\{@?\w{1,}\}"); 2257 | #if LIBLOG_PORTABLE 2258 | private static readonly Regex Pattern = new Regex(@"(?[^\d{][^ }]*)}"); 2259 | #else 2260 | private static readonly Regex Pattern = new Regex(@"(?[^ :{}]+)(?:[^}]+)?}", RegexOptions.Compiled); 2261 | #endif 2262 | 2263 | /// 2264 | /// Some logging frameworks support structured logging, such as serilog. This will allow you to add names to structured data in a format string: 2265 | /// For example: Log("Log message to {user}", user). This only works with serilog, but as the user of LibLog, you don't know if serilog is actually 2266 | /// used. So, this class simulates that. it will replace any text in {curly braces} with an index number. 2267 | /// 2268 | /// "Log {message} to {user}" would turn into => "Log {0} to {1}". Then the format parameters are handled using regular .net string.Format. 2269 | /// 2270 | /// The message builder. 2271 | /// The format parameters. 2272 | /// 2273 | public static Func SimulateStructuredLogging(Func messageBuilder, object[] formatParameters) 2274 | { 2275 | if (formatParameters == null || formatParameters.Length == 0) 2276 | { 2277 | return messageBuilder; 2278 | } 2279 | 2280 | return () => 2281 | { 2282 | string targetMessage = messageBuilder(); 2283 | IEnumerable patternMatches; 2284 | return FormatStructuredMessage(targetMessage, formatParameters, out patternMatches); 2285 | }; 2286 | } 2287 | 2288 | private static string ReplaceFirst(string text, string search, string replace) 2289 | { 2290 | int pos = text.IndexOf(search, StringComparison.Ordinal); 2291 | if (pos < 0) 2292 | { 2293 | return text; 2294 | } 2295 | return text.Substring(0, pos) + replace + text.Substring(pos + search.Length); 2296 | } 2297 | 2298 | public static string FormatStructuredMessage(string targetMessage, object[] formatParameters, out IEnumerable patternMatches) 2299 | { 2300 | if (formatParameters.Length == 0) 2301 | { 2302 | patternMatches = Enumerable.Empty(); 2303 | return targetMessage; 2304 | } 2305 | 2306 | List processedArguments = new List(); 2307 | patternMatches = processedArguments; 2308 | 2309 | foreach (Match match in Pattern.Matches(targetMessage)) 2310 | { 2311 | var arg = match.Groups["arg"].Value; 2312 | 2313 | int notUsed; 2314 | if (!int.TryParse(arg, out notUsed)) 2315 | { 2316 | int argumentIndex = processedArguments.IndexOf(arg); 2317 | if (argumentIndex == -1) 2318 | { 2319 | argumentIndex = processedArguments.Count; 2320 | processedArguments.Add(arg); 2321 | } 2322 | 2323 | targetMessage = ReplaceFirst(targetMessage, match.Value, 2324 | "{" + argumentIndex + match.Groups["format"].Value + "}"); 2325 | } 2326 | } 2327 | try 2328 | { 2329 | return string.Format(CultureInfo.InvariantCulture, targetMessage, formatParameters); 2330 | } 2331 | catch (FormatException ex) 2332 | { 2333 | throw new FormatException("The input string '" + targetMessage + "' could not be formatted using string.Format", ex); 2334 | } 2335 | } 2336 | } 2337 | 2338 | #if !LIBLOG_PORTABLE 2339 | [ExcludeFromCodeCoverage] 2340 | #endif 2341 | internal static class TypeExtensions 2342 | { 2343 | internal static ConstructorInfo GetConstructorPortable(this Type type, params Type[] types) 2344 | { 2345 | #if LIBLOG_PORTABLE 2346 | return type.GetTypeInfo().DeclaredConstructors.FirstOrDefault 2347 | (constructor => 2348 | constructor.GetParameters() 2349 | .Select(parameter => parameter.ParameterType) 2350 | .SequenceEqual(types)); 2351 | #else 2352 | return type.GetConstructor(types); 2353 | #endif 2354 | } 2355 | 2356 | internal static MethodInfo GetMethodPortable(this Type type, string name) 2357 | { 2358 | #if LIBLOG_PORTABLE 2359 | return type.GetRuntimeMethods().SingleOrDefault(m => m.Name == name); 2360 | #else 2361 | return type.GetMethod(name); 2362 | #endif 2363 | } 2364 | 2365 | internal static MethodInfo GetMethodPortable(this Type type, string name, params Type[] types) 2366 | { 2367 | #if LIBLOG_PORTABLE 2368 | return type.GetRuntimeMethod(name, types); 2369 | #else 2370 | return type.GetMethod(name, types); 2371 | #endif 2372 | } 2373 | 2374 | internal static PropertyInfo GetPropertyPortable(this Type type, string name) 2375 | { 2376 | #if LIBLOG_PORTABLE 2377 | return type.GetRuntimeProperty(name); 2378 | #else 2379 | return type.GetProperty(name); 2380 | #endif 2381 | } 2382 | 2383 | internal static IEnumerable GetFieldsPortable(this Type type) 2384 | { 2385 | #if LIBLOG_PORTABLE 2386 | return type.GetRuntimeFields(); 2387 | #else 2388 | return type.GetFields(); 2389 | #endif 2390 | } 2391 | 2392 | internal static Type GetBaseTypePortable(this Type type) 2393 | { 2394 | #if LIBLOG_PORTABLE 2395 | return type.GetTypeInfo().BaseType; 2396 | #else 2397 | return type.BaseType; 2398 | #endif 2399 | } 2400 | 2401 | #if LIBLOG_PORTABLE 2402 | internal static MethodInfo GetGetMethod(this PropertyInfo propertyInfo) 2403 | { 2404 | return propertyInfo.GetMethod; 2405 | } 2406 | 2407 | internal static MethodInfo GetSetMethod(this PropertyInfo propertyInfo) 2408 | { 2409 | return propertyInfo.SetMethod; 2410 | } 2411 | #endif 2412 | 2413 | #if !LIBLOG_PORTABLE 2414 | internal static object CreateDelegate(this MethodInfo methodInfo, Type delegateType) 2415 | { 2416 | return Delegate.CreateDelegate(delegateType, methodInfo); 2417 | } 2418 | #endif 2419 | 2420 | internal static Assembly GetAssemblyPortable(this Type type) 2421 | { 2422 | #if LIBLOG_PORTABLE 2423 | return type.GetTypeInfo().Assembly; 2424 | #else 2425 | return type.Assembly; 2426 | #endif 2427 | } 2428 | } 2429 | 2430 | #if !LIBLOG_PORTABLE 2431 | [ExcludeFromCodeCoverage] 2432 | #endif 2433 | internal class DisposableAction : IDisposable 2434 | { 2435 | private readonly Action _onDispose; 2436 | 2437 | public DisposableAction(Action onDispose = null) 2438 | { 2439 | _onDispose = onDispose; 2440 | } 2441 | 2442 | public void Dispose() 2443 | { 2444 | if (_onDispose != null) 2445 | { 2446 | _onDispose(); 2447 | } 2448 | } 2449 | } 2450 | } 2451 | --------------------------------------------------------------------------------