/// <summary>
        /// Analyze the provided session.
        /// </summary>
        /// <param name="session">The session to be analyzed</param>
        /// <remarks>
        /// <para>
        /// The FogBugz configuration file is read for each session so that
        /// changes to the file can be made without having to restart Loupe.
        /// </para>
        /// </remarks>
        public void Process(ISession session)
        {
            //if we aren't supposed to run on this tier, don't.
            if (m_Context.Environment == LoupeEnvironment.Server)
            {
                if (m_Controller.HubConfiguration.EnableAutomaticAnalysis == false)
                {
                    Log.Verbose(LogCategory, "Skipping session because automatic session analysis not enabled on the Hub", null);
                }
            }
            else
            {
                if (m_Controller.UserConfiguration.EnableAutomaticAnalysis == false)
                {
                    Log.Verbose(LogCategory, "Skipping session because automatic session analysis is not enabled", null);
                }
            }

            // Ignore sessions that do not contain any errors
            if (session.ErrorCount == 0)
            {
                Log.Verbose(LogCategory, "Skipping session because there are no errors to inspect", null);
                return;
            }

            // Only process this session if there is a matching target project in FogBugz
            Mapping target = m_Controller.FindTarget(session);

            if (target == null)
            {
                Log.Verbose(LogCategory, "Skipping session because it doesn't match any target in the configuration file", null);
                return; // this could be perfectly normal
            }

            // Throws an exception if we can't connect with FogBugz
            FBApi api;

            try
            {
                api = m_Controller.GetApi();
            }
            catch (Exception ex)
            {
                Log.Error(ex, LogCategory, "Unable to analyze session because FogBugz server is not available", "When connecting to the FogBugz API an exception was thrown.  We will assume it is not available.");
                throw; //we really do want to abort out to our caller who is designed to handle failures like this.
            }

            // Process each unique error (as defined in IssuesByClass)
            Dictionary <string, ErrorInfo> errorList = new Dictionary <string, ErrorInfo>();

            foreach (ILogMessage message in session.GetMessages())
            {
                //process errors
                if (message.Severity > LogMessageSeverity.Error)
                {
                    continue;
                }

                ErrorInfo currentError = new ErrorInfo(message);
                ErrorInfo previousError;

                // This error is either new or another instance of a previous error
                if (errorList.TryGetValue(currentError.Fingerprint, out previousError))
                {
                    previousError.Messages.Add(message);
                }
                else
                {
                    errorList.Add(currentError.Fingerprint, currentError);
                }
            }

            foreach (KeyValuePair <string, ErrorInfo> pair in errorList)
            {
                ErrorInfo         error       = pair.Value;
                FogBugzCaseWriter fogBugzCase = new FogBugzCaseWriter(Log, error);
                fogBugzCase.Submit(api, target);
            }
        }