April 20, 2016

 

A Little Bit More Convenient LogFile Class

All kinds of programs have a prevalent need to write log files. You want your program to basically "keep notes" of it's progress at significant stages, and note various pieces of relevant information (e.g., you read in an input file and you want to know the number of lines [records], and maybe you're counting or summing lines, or pieces of data in those lines, in various categories, and you want the program to note this information in the log file as it is proceeding). Of course, if certain events happen - especially in the case of errors where the program can't proceed to do what it's supposed to do - then you want the program to note this in the log file. This way when your program is finished, or terminates on error, you can look at the information in the log file to see the results, or determine what went wrong so you know what you need to do to fix the problem. (For example, maybe a necessary input file that is produced by some other process hasn't been produced yet, so you have a file existence check, or perhaps some kind of file data integrity check, built into your program which, if triggered, will terminate gracefully and note the problem in the log file.)

(By the way, don't make the mistake that some NASA programmer(s) made with the Spirit rover computer that landed on Mars, which maxed out its data storage space a couple of weeks after it landed because the programmer(s) neglected to put some sort of limiter on log files. This is a topic for another article - I won't be covering it in the sample code here - but in an actual production program you must be sure to have code that implements some sort of method to limit the space that log files take up. For example, a very simple implementation would be to count the number of log files in a log file directory that the program is writing its log file to, and once it exceeds some set number, then delete the oldest log files until you get to less than the limit. Another method would be to sum the actual sizes of the log files, and then if the maximum size taken by all the log files exceeds some set amount, then delete the oldest log files until you get to less than the size limit.)

For my own usage with my C# programs, I dug around on the Internet a bit, and decided to use the "Convenient LogFile Class" by Jonathan Wood. The bulk of the code here is from his original code, but I've made a number of changes to the details, and added a few functions, so I'm calling this "A Little Bit More Convenient LogFile Class

The entirety of the code for the class is shown at the bottom.

Instantiating a LogFile Object

The general context of my discussion here is a Console Application. Obviously, if your context is different, then you'll do things differently. Typically I'm starting up my log file as soon as the program starts, so (for a Console App) I do this as the very first thing in my Main() function, like so:

static int Main(string[] args) {
  LogFile logfile = new LogFile(filename: file_log, keepOpen: true);
  if (!logfile.Status) {
    Console.WriteLine(logfile.StatusMessage);
    return 16;
  }
  logfile.WriteLine("{0} started", progname);

  ...

Note: This LogFile class is designed to automatically prepend what you give to .WriteLine() with a timestamp (see at the bottom the code for the class method WriteLine() - which actually feeds to a Write() method; there you'll see the usage of the DateTime.Now.ToString() function).

In my typical usage, I create this logfile object and pass it around where it needs to be used - and this is why I've turned on "keepOpen" (set it to true). If you don't turn this flag on, then the object will close the file after each write, and reopen the file in append mode before each write after the first one. Obviously, the most efficient operation is to keep the file open. (So note that there are no considerations for asynchronous or parallel processing with this. As described in this article, this is strictly for the context of a synchronous process.)

That "file_log" variable you see, I've defined in some "prefatory" code at the beginning of the Program class before any methods, like so:

namespace LogFileTest {
  class Program {
    const string progname = "LogFileTest";

    const string file_log_base = "log_" + progname + "_";
    const string file_log_ext = ".txt";
    static readonly string datetime_start = DateTime.Now.ToString("yyMMddHHmmss");
    static readonly string file_log = Path.Combine(dir_base_dat, file_log_base + datetime_start + file_log_ext);

    ...

(You need a "using System.IO;" for the "Path" object, if you don't already have it.)

I added some code to the constructor method to catch exceptions related to the path you provide, and if any such exceptions are encountered then the "Status" of the object will be set to false, so immediately after instantiation, you can check this status to see if things are okay with the LogFile object.

Using the LogFile Object

For methods where you are writing to the log file, you would just put in something like "LogFile log" as one of the parameters. Here is a trivial, totally out-of-context example:

static int GetWebData(LogFile log) {
  bool bBad = false;
  IniParser iniparser;
  string file_cfg = @"C:\prog\getwebdata\cfg\getwebdata.ini";
  string x;
  try { iniparser = new IniParser(file_cfg); }
  catch (Exception ex) {
    x = ex.Message;
    logfile.WriteLine("error: INI file parser failed on \"{0}\"", file_cfg);
    logfile.WriteLine("exception message: {0}", x);
    return -1;
  }

  ...

The method call would look something like this:

int rv = GetWebData(logfile);

If you need to flush the stream, which you may want to do at critical points, just do this (where the LogFile object is being referenced by the name "log"):

log.Flush();

When you're finished with the log file, just do this:

logfile.Close();

The LogFile Class

using System;
using System.IO;
using System.Security;

// derived from: http://www.blackbeltcoder.com/Articles/files/a-convenient-logfile-class

namespace UtilBasic {
  public class LogFile : IDisposable {
    protected string _filename;
    protected StreamWriter _writer;
    protected bool _disposed;
    protected bool _status;
    protected string _status_message;

    /// <summary>
    /// Gets the current filename
    /// </summary>
    public string Filename { get { return _filename; } }

    /// <summary>
    /// Gets or sets if the file is closed between each call to Write()
    /// </summary>
    public bool KeepOpen { get; set; }

    /// <summary>
    /// Gets or sets if logging is active or suppressed
    /// </summary>
    public bool IsLogging { get; set; }

    public bool Status { get { return _status; } }

    public string StatusMessage { get { return _status_message; } }

    /// <summary>
    /// Constructs a new LogFile object.
    /// </summary>
    /// <param name="filename">Name of log file to write.</param>
    /// <param name="append">If true, data is written to the end of any
    /// existing data; otherwise, existing data is overwritten.</param>
    /// <param name="keepOpen">If true, performance is improved by
    /// keeping the file open between writes; otherwise, the file
    /// is opened and closed for each write.</param>
    public LogFile(string filename, bool append = true, bool keepOpen = false) {
      _status = true;
      string filepath = null;
      try { filepath = Path.GetFullPath(filename); }
      catch (ArgumentException ex) { _status_message += ex.Message; _status = false; }
      catch (SecurityException ex) { _status_message += ex.Message; _status = false; }
      catch (NotSupportedException ex) { _status_message += ex.Message; _status = false; }
      catch (PathTooLongException ex) { _status_message += ex.Message; _status = false; }

      if (_status == false) {
        _filename = null;
        IsLogging = false;
        KeepOpen = false;
        _disposed = true;
        return;
      }

      _filename = filepath;

      string dir = null;
      if (_filename != null) {
        try { dir = Path.GetDirectoryName(_filename); }
        catch (ArgumentException ex) { _status_message += ex.Message; _status = false; }
        catch (PathTooLongException ex) { _status_message += ex.Message; _status = false; }
      }

      if (_status == false) {
        _filename = null;
        IsLogging = false;
        KeepOpen = false;
        _disposed = true;
        return;
      }

      if (!Directory.Exists(dir)) {
        try { Directory.CreateDirectory(dir); }
        catch (ArgumentNullException ex) { _status_message = ex.Message; _status = false; }
        catch (ArgumentException ex) { _status_message = ex.Message; _status = false; }
        catch (NotSupportedException ex) { _status_message = ex.Message; _status = false; }
        catch (PathTooLongException ex) { _status_message = ex.Message; _status = false; }
        catch (DirectoryNotFoundException ex) { _status_message = ex.Message; _status = false; }
        catch (IOException ex) { _status_message = ex.Message; _status = false; }
        catch (UnauthorizedAccessException ex) { _status_message = ex.Message; _status = false; }
      }

      if (_status == false) {
        _filename = null;
        IsLogging = false;
        KeepOpen = false;
        _disposed = true;
        return;
      }

      _status_message = "Good";
      KeepOpen = keepOpen;
      IsLogging = true;

      _writer = null;
      _disposed = false;

      // Delete existing file if not appending (ignore exceptions)
      if (!append) {
        try {
          File.Delete(_filename);
        }
        catch { }
      }
    }

    /// <summary>
    /// Closes the current log file
    /// </summary>
    public void Close() {
      if (_writer != null) {
        _writer.Dispose();
        _writer = null;
      }
    }

    /// <summary>
    /// Writes a formatted string to the current log file
    /// </summary>
    /// <param name="fmt"></param>
    /// <param name="args"></param>
    public void Write(string fmt, params object[] args) {
      Write(String.Format(fmt, args));
    }

    /// <summary>
    /// Writes a string to the current log file
    /// </summary>
    /// <param name="s"></param>
    public void Write(string s, bool bAddNewLine = false) {
      if (IsLogging) {
        // Establish file stream if needed
        if (_writer == null) _writer = new StreamWriter(_filename, true);

        // Write string with date/time stamp
        if (bAddNewLine) _writer.WriteLine(String.Format("[{0}] {1}", DateTime.Now.ToString("yyMMddHHmmss"), s));
        else _writer.Write(String.Format("[{0}] {1}", DateTime.Now.ToString("yyMMddHHmmss"), s));

        // Close file if not keeping open
        if (!KeepOpen) Close();
      }
    }

    /// <summary>
    /// Writes a formatted string to the current log file
    /// </summary>
    /// <param name="fmt"></param>
    /// <param name="args"></param>
    public void WriteLine(string fmt, params object[] args) {
      WriteLine(String.Format(fmt, args));
    }

    /// <summary>
    /// Writes a string to the current log file
    /// </summary>
    /// <param name="s"></param>
    public void WriteLine(string s) {
      Write(s, true);
    }

    /// <summary>
    /// Flushes the stream
    /// </summary>
    public void Flush() {
      if (IsLogging) {
        if (_writer == null) return;
        _writer.Flush();
      }
    }

    #region IDisposable Members

    public void Dispose() {
      Dispose(true);
      GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing) {
      if (_disposed) { return; }
      // Need to dispose managed resources if being called manually
      if (disposing) Close();
      _disposed = true;
    }

    #endregion
  }
}

April 17, 2016

 

How to use Firebird database with a C# program - Part 1 (Preparation)

In this article and in the subsequent articles, I will show you how to write C# programs that use the embedded version of the Firebase database server. In this article, I explain how to get started.

If you know the steps, this is actually pretty easy. It's not calculus. But therein lies the rub, no?

Preparation Steps Overview

  1. Get latest version of Firebird.
  2. Start new C# project.
  3. Put relevant Firebird DLL's and .msg file in project subdirectory.
  4. Add Firebird ADO.NET provider to project.
  5. Get latest version of FlameRobin (or other database admin that works with Firebird).

1. Get latest version of Firebird.

You can download Firebird from the Firebird web site, here:

Firebird downloads

Get the embedded server. If you've already downloaded Firebird at some time, and you're using FlameRobin, here's how you can see what Firebird version you have:

Server -> Retrieve server version

You'll see something like this: "WI-V2.5.4.26856 Firebird 2.5"

2. Start new C# project.

What it says. In my case, for my own testing/usage, I've been using Visual Studio Express 2013 for Desktop and now VSE 2015 for Desktop, and just programming Console Application projects. But obviously you could use Firebird with, say, a Class Library project, or a WPF Application project.

3. Put relevant Firebird DLL's and .msg file in project subdirectory.

I've been creating a subdirectory within my project directory, and copying the relevant Firebird DLL's and ".msg" file there. Thus, for each project I have that uses the Firebird database engine (the embedded server), it is using it's own separate copies of the Firebird files. I haven't tried programming multiple projects to use the same Firebird files in a single directory, so I can't tell you anything about issues regarding concurrent program usages.

In my particular case, I just make a subdirectory called "dbengine" within my project folder, and then I copy the Firebird files into it. Here is a list of the files you need, from your download from the Firebird site:

If you want to use UDF's (user defined functions) with Firebird, then you will also need to copy the DLL "ib_util.dll". Otherwise, you don't need it. (Note that the Firebird engine will generate a log file "firebird.log" in the subdirectory, and if it doesn't see this "ib_util.dll" file it puts a warning message about not finding it in the log.)

4. Add Firebird ADO.NET provider to project.

The easiest way to do this is to use NuGet. The way this works with VSE 2015 is that in the Solution Explorer panel you right-click on the "References" entry under your project, then click on "Manage NuGet packages...". A NuGet tab window comes up in VSE. Click on the "Browse" sub-tab, and enter "FirebirdSql.Data.FirebirdClient" in the Search box. The entry for this package should show up in the search results below that (along with perhaps some other irrelevant results), and you just click on it, then click the "Install" button that will show up toward the right. This will install the Firebird ADO.NET provider package to your project.

(Obviously, if you haven't installed NuGet, you need to do this first. You can do this through Tools -> Extensions and Updates from the main menu of VSE.)

When this is installed, then in the "References" list of your project (in the Solution Explorer panel), you'll see "FirebirdSql.Data.FirebirdClient".

5. Get latest version of FlameRobin.

It's always nice to use a GUI database admin utility program to work with your database engine, and FlameRobin is kind of synonymous with Firebird. I would suppose there are alternatives (or not; I have no idea), but I loaded FlameRobin and have been using it and it has suited my needs fine, so I haven't checked into using any other database admin programs with Firebird, so I can't give you any advice in that direction.

You can download FlameRobin from the FlameRobin web site, here:

FlameRobin

As far as I know, you can't create a new database programmatically, so you have to use "isql" or a database admin program (that works with Firebird) in order to create the database(s) you'll be using with your program. Once the database is created, then everything else can be done from within your program in regard to creating tables, indexes, constraints, and so on. To create a new database with FlameRobin:

Database -> Create new database

You'll get a dialog box with these fields:

Display name:
Database path:
Authentication [method]:
User name:
Password:

The "display name" is the name used in FlameRobin, but it's the "database path" that actually specifies the path and the name of the database file. For example, "D:\prog\db\FirebirdTest\FBDB.FDB". This can, of course, be whatever you want it to be.

Miscellaneous Reference Links


June 25, 2012

 

Using 2D static char array in a function (C programming)

Obviously, there are a number of different ways to do this - but this seems to me to be the most simple, basic way to do this.

Say that in your main() you've set up a 2D char array (or you can think of it as an array of "strings"), like so (note that I'm using the gcc compiler, with C99 option):


  const unsigned int sz_addr = 50 + 1;
  const unsigned int sz_faddr = 10;
  char faddr[sz_faddr][sz_addr];

  // initialize
  for (unsigned char i=0;i<sz_faddr;++i) { strcpy(faddr[i],""); }

Now, using the array right there in main() is straightforward:


  // put some data in the array
  strcpy(faddr[0],"one");
  strcpy(faddr[1],"two");
  strcpy(faddr[2],"three");

But let's say you want to pass the 2D char array to a function (along with some other pieces of information perhaps), then do whatever it is you're doing and filling the 2D char array in the function based on your analysis. (In my recent application I'm working on, I'm actually passing two different 2D char arrays to a function, then analyzing the information in the first set of "strings" and manipulating them and putting the results in the second set of "strings".)

The point here (no pun intended) is that we're using a static 2D char array, not a char** variable that has had memory dynamically allocated to it, so you can't just directly pass pointers through the function, since the variable, "faddr" in this example, is not pointers, but a name of a static 2D array. But just saying this this way gives us the simple answer: Just make a pointer reference to the static 2D array, fill it in with the appropriate memory addresses (of where the "strings" are located), then pass the pointer variable to a function. (There is absolutely nothing of any real usefulness in this particular example function; this is merely to demonstrate the basic function parameter method.)

Like so:


#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void testpp(char**,unsigned int,unsigned int);

int main(void) {
  const unsigned int sz_addr = 50 + 1;
  const unsigned int sz_faddr = 10;
  char faddr[sz_faddr][sz_addr];

  // initialize
  for (unsigned char i=0;i<sz_faddr;++i) { strcpy(faddr[i],""); }

  // enter some data (first 5 "strings" only)
  strcpy(faddr[0],"one");
  strcpy(faddr[1],"two");
  strcpy(faddr[2],"three");
  strcpy(faddr[3],"four");
  strcpy(faddr[4],"five");

  // define and allocate a pointer to a bunch of char pointers
  char** pfaddr = malloc(sz_faddr*sizeof(char*));

  // initialize pointer with addresses of the beginning of each
  // "string" of the static 2D char array
  for (unsigned int i=0;i<sz_faddr;++i) { *(pfaddr+i) = &faddr[i][0]; }

  // pass the pointer to the function
  testpp(pfaddr,sz_faddr,sz_addr);

  // print "strings" to stdout (first 5 only)
  printf("%s\n",faddr[0]);
  printf("%s\n",faddr[1]);
  printf("%s\n",faddr[2]);
  printf("%s\n",faddr[3]);
  printf("%s\n",faddr[4]);

  free(pfaddr); // deallocation
  return(0);
}

void testpp(char** px,unsigned int m,unsigned int n) {
  strcpy(*(px+0),"eleven");
  strcpy(*(px+1),"twelve");
  strcpy(*(px+2),"thirteen");
  return;
}

Please note the following: Even though I've defined the function, as shown here, to include the two sizes information ("m" and "n"), I've written only very basic code and am not actually using the information at all, merely using the strcpy() function inside the testpp() function, so this certainly is not security-safe code in any way (even though I'm showing the "m" and "n" for such facility). This example merely shows a method for how to use a static 2D char array and working with that in a function through the intermediary of an array of pointers to the "strings" of the array. In a "production" application with variables holding data coming from some user input (say, from an input data file), you'd obviously want to be checking lengths and truncating at the array maximum lengths (allowing one char for the null-terminator).


July 07, 2005

 

Regular expression snippet for datetime format

Note that due to line-wrapping you may miss the embedded spaces in this. So to make it explicit there's a space after the date portion (that ends with "(?:0[5-9]|[1-2][0-9])") and there's a space followed by a question mark right before the "AM/PM" stuff.

Here you go, this is a long one...
/^(?:0?[1-9]|1[0-2])[-\/](?:0?[1-9]|[1-2][0-9]|3[0-1])[-\/](?:20)?(?:0[5-9]|[1-2][0-9]) (?:(?:0|0?[1-9]|1[0-1]):[0-5][0-9]:[0-5][0-9] ?(?:[AP]M[ap]m))(?:(?:0|0?[1-9]|1[0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9])$/

If you're wondering what the "?:" is all about, it's a notation to the regex engine telling it to not worry about keeping track of any backreferences (which speeds things up). So "(?:expression)" means that the captured expression won't go into any \1 or $1 reference (if you don't need it), and the regex evaluation will run faster.

In Perl for example you'd use this expression to validate a variable holding a datetime value (making sure the variable is left- and right-trimmed of whitespace; or you can modify the regular expression to account for possible left- or right-whitespace), like this:
if ($dt_str !~ m/^{mess}$/)

which means you're checking to see if the "$dt_str" variable value does not match the "{mess}" pattern (where {mess} is the long regular expression above).

Note that the year part as I've written it here validates for years from 2005 to 2029 (and the "20" is optional) which fits what I need it to do, so you may need to change the years being checked for your own needs. As I've written it it allows for a date format like "mm-dd/yy" or "mm/dd-yy" but to force consistency in the delimiters would have made it just so much longer and I didn't feel like that one was very important, because I can still get the relevant data from that. In Perl, you could force consistency on the delimiters like this
$dt_str =~ s/^(\d?\d)[-\/](\d?\d)[-\/]((?:\d\d)?\d\d)/\1-\2-\3/;

which forces the date delimiters to be dashes. Note also that the expression here allows for either AM/PM format or for military time (00:00:00 to 23:59:59), and it allows for upper- or lowercase AM/PM.

I hope this is useful to you.

This page is powered by Blogger. Isn't yours?