#1
  1. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Nov 2003
    Location
    Edinburgh, UK
    Posts
    84
    Rep Power
    11

    Log4Net Connection issues with MySQL


    Hi there,

    I am having problems with using Log4net to log to a MySql DB.

    I am setting up my log as so:

    Code:
    using log4net;
    using MySql.Data.MySqlClient;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace Logger.Setup
    {
        public class SetUpLog
        {
            internal log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); 
            internal SetUpLog(string dbconn)
            {
                log4net.Repository.Hierarchy.Hierarchy log = 
                    log4net.LogManager.GetRepository() as log4net.Repository.Hierarchy.Hierarchy;
                if (log != null)
                {
                    log4net.Appender.AdoNetAppender adoAppender = (log4net.Appender.AdoNetAppender)
                        log.GetLogger("Test", log.LoggerFactory).GetAppender("ADONetAppender");
                    if(adoAppender != null)
                    {
                        adoAppender.ConnectionType = "MySql.Data.MySqlClient.MySqlConnection, Version=6.0.3.0, Culture=neutral";                    
                        adoAppender.ConnectionString = dbconn;
                        adoAppender.ActivateOptions();
                    }
                }
                log4net.Config.XmlConfigurator.Configure();
            }
        }
    }
    and my app.config looks like this:

    Code:
    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      <configSections>
        <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
      </configSections>
      <log4net>
        <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
          <layout type="log4net.Layout.PatternLayout">
            <conversionPattern value="%date [%thread] %-5level %message%newline" />
          </layout>
        </appender>
    
        <appender name="ADONetAppender" type="log4net.Appender.ADONetAppender">
        <bufferSize value="1" />
          <connectionString value="Provider=MySqlProv;Server=localhost;Database=logDB;Uid=root;Pwd=password;" />
        <commandText value="insert into logdb.mylog(ID,DateTime,Thread,Level,Message)values(2,@log_date,@thread,@log_level,@message)" />
        <parameter>
          <parameterName value="@log_date" />
          <dbType value="DateTime" />
          <layout type="log4net.Layout.RawTimeStampLayout" />
        </parameter>
        <parameter>
          <parameterName value="@thread" />
          <dbType value="String" />
          <size value="255" />
          <layout type="log4net.Layout.PatternLayout">
            <conversionPattern value="%t" />
          </layout>
        </parameter>
        <parameter>
          <parameterName value="@log_level" />
          <dbType value="String" />
          <size value="50" />
          <layout type="log4net.Layout.PatternLayout">
            <conversionPattern value="%p" />
          </layout>
        </parameter>
        <parameter>
          <parameterName value="@message" />
          <dbType value="String" />
          <size value="4000" />
          <layout type="log4net.Layout.PatternLayout">
            <conversionPattern value="%m" />
          </layout>
        </parameter>
        </appender>
    
        <root>
          <level value="DEBUG" />
          <appender-ref ref="ConsoleAppender" />
          <appender-ref ref="ADONetAppender" />
        </root>
      </log4net>
      <connectionStrings>
        <add name="LogDB" connectionString="Provider=MySql;Server=localhost;Database=logDB;Uid=root;Pwd=password;"/>
      </connectionStrings>
    </configuration>
    However, I seem to have two problems. First up - I dont seem to be able to initialise my connection string using the app.config, so I am forced into having it present in the log4net config section. This isnt such a problem for me now, the biggest problem is that I keep getting the following error when executing a test client:

    C:\Users\User\Documents\Visual Studio 2008\Projects\Logger\TestClient\bin\Debug
    >TestClient.exe
    log4net:ERROR [AdoNetAppender] Could not open database connection [Provider=MySq
    lProv;Server=localhost;Database=logDB;Uid=root;Pwd=password;]
    System.InvalidOperationException: The 'MySqlProv' provider is not registered on
    the local machine.
    at System.Data.OleDb.OleDbServicesWrapper.GetDataSource(OleDbConnectionString
    constr, DataSourceWrapper& datasrcWrapper)
    at System.Data.OleDb.OleDbConnectionInternal..ctor(OleDbConnectionString cons
    tr, OleDbConnection connection)
    at System.Data.OleDb.OleDbConnectionFactory.CreateConnection(DbConnectionOpti
    ons options, Object poolGroupProviderInfo, DbConnectionPool pool, DbConnection o
    wningObject)
    at System.Data.ProviderBase.DbConnectionFactory.CreateNonPooledConnection(DbC
    onnection owningConnection, DbConnectionPoolGroup poolGroup)
    at System.Data.ProviderBase.DbConnectionFactory.GetConnection(DbConnection ow
    ningConnection)
    at System.Data.ProviderBase.DbConnectionClosed.OpenConnection(DbConnection ou
    terConnection, DbConnectionFactory connectionFactory)
    at System.Data.OleDb.OleDbConnection.Open()
    at log4net.Appender.AdoNetAppender.InitializeDatabaseConnection()
    Provider=MySql;Server=localhost;Database=logDB;Uid=root;Pwd=password;
    2009-05-18 21:59:36,391 [1] INFO Hello!
    (The very last line is my ConsoleAppender).

    Now, I am not using the OLEDB driver at all - I have the .Net connector installed. So why does it whinge at this? Could someone tell me if log4net works with the .Net connector for MySql? (I am specifying a provider above just for the sake of testing right now.)
  2. #2
  3. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Nov 2003
    Location
    Edinburgh, UK
    Posts
    84
    Rep Power
    11
    I actually got past this problem on my own eventually. I wont post the code for it here just now as it isnt really in a very good state - plus there are still a few teething problems. As soon as there are sorted, I will provide my soloution.

    Originally Posted by karym6
    Hi there,

    I am having problems with using Log4net to log to a MySql DB.

    I am setting up my log as so:

    Code:
    using log4net;
    using MySql.Data.MySqlClient;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace Logger.Setup
    {
        public class SetUpLog
        {
            internal log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); 
            internal SetUpLog(string dbconn)
            {
                log4net.Repository.Hierarchy.Hierarchy log = 
                    log4net.LogManager.GetRepository() as log4net.Repository.Hierarchy.Hierarchy;
                if (log != null)
                {
                    log4net.Appender.AdoNetAppender adoAppender = (log4net.Appender.AdoNetAppender)
                        log.GetLogger("Test", log.LoggerFactory).GetAppender("ADONetAppender");
                    if(adoAppender != null)
                    {
                        adoAppender.ConnectionType = "MySql.Data.MySqlClient.MySqlConnection, Version=6.0.3.0, Culture=neutral";                    
                        adoAppender.ConnectionString = dbconn;
                        adoAppender.ActivateOptions();
                    }
                }
                log4net.Config.XmlConfigurator.Configure();
            }
        }
    }
    and my app.config looks like this:

    Code:
    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      <configSections>
        <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
      </configSections>
      <log4net>
        <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
          <layout type="log4net.Layout.PatternLayout">
            <conversionPattern value="%date [%thread] %-5level %message%newline" />
          </layout>
        </appender>
    
        <appender name="ADONetAppender" type="log4net.Appender.ADONetAppender">
        <bufferSize value="1" />
          <connectionString value="Provider=MySqlProv;Server=localhost;Database=logDB;Uid=root;Pwd=password;" />
        <commandText value="insert into logdb.mylog(ID,DateTime,Thread,Level,Message)values(2,@log_date,@thread,@log_level,@message)" />
        <parameter>
          <parameterName value="@log_date" />
          <dbType value="DateTime" />
          <layout type="log4net.Layout.RawTimeStampLayout" />
        </parameter>
        <parameter>
          <parameterName value="@thread" />
          <dbType value="String" />
          <size value="255" />
          <layout type="log4net.Layout.PatternLayout">
            <conversionPattern value="%t" />
          </layout>
        </parameter>
        <parameter>
          <parameterName value="@log_level" />
          <dbType value="String" />
          <size value="50" />
          <layout type="log4net.Layout.PatternLayout">
            <conversionPattern value="%p" />
          </layout>
        </parameter>
        <parameter>
          <parameterName value="@message" />
          <dbType value="String" />
          <size value="4000" />
          <layout type="log4net.Layout.PatternLayout">
            <conversionPattern value="%m" />
          </layout>
        </parameter>
        </appender>
    
        <root>
          <level value="DEBUG" />
          <appender-ref ref="ConsoleAppender" />
          <appender-ref ref="ADONetAppender" />
        </root>
      </log4net>
      <connectionStrings>
        <add name="LogDB" connectionString="Provider=MySql;Server=localhost;Database=logDB;Uid=root;Pwd=password;"/>
      </connectionStrings>
    </configuration>
    However, I seem to have two problems. First up - I dont seem to be able to initialise my connection string using the app.config, so I am forced into having it present in the log4net config section. This isnt such a problem for me now, the biggest problem is that I keep getting the following error when executing a test client:



    (The very last line is my ConsoleAppender).

    Now, I am not using the OLEDB driver at all - I have the .Net connector installed. So why does it whinge at this? Could someone tell me if log4net works with the .Net connector for MySql? (I am specifying a provider above just for the sake of testing right now.)
  4. #3
  5. No Profile Picture
    I haz teh codez!
    Devshed Frequenter (2500 - 2999 posts)

    Join Date
    Dec 2003
    Posts
    2,548
    Rep Power
    2337
    Thanks for coming back for the update!
  6. #4
  7. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Nov 2003
    Location
    Edinburgh, UK
    Posts
    84
    Rep Power
    11
    Originally Posted by ptr2void
    Thanks for coming back for the update!
    No worries, log4net is very powerful, but quite hard to get to grips with imo, so it is only fair I share what I have been able to do...

    Ok, this is my working soloution right now - with one small caveat, it generates a non fatal error.

    below is some code you can use for a completee class to handle logging with log4net, it works no problems.

    Code:
    using log4net;
    using log4net.Appender;
    using log4net.Repository.Hierarchy;
    using System;
    using System.Collections.Generic;
    using MySql.Data.MySqlClient;
    using System.Linq;
    using System.Text;
    
    namespace Logger.Setup
    {
        public class SetUpLog
        {
            /*****************************************************************************/
            /// <summary>
            /// Use this constructor if you only want to log to the console or to a file.
            /// </summary>
            internal SetUpLog()
            {
                log4net.Config.XmlConfigurator.Configure();
            }
            /*****************************************************************************/
            /// <summary>
            /// Use this constructor if you want to log to a database. If you choose this option
            /// you need to make sure that the application driving the process has the correct 
            /// configuration in its app.config
            /// </summary>
            /// <param name="dbConn">string representing the data base connection</param>
            public SetUpLog(string dbConn)
            {
                log4net.Config.XmlConfigurator.Configure();
                createAdoSettings(dbConn);
            }
            /*****************************************************************************/
            /// <summary>
            /// Defines a hierachy for use with log4net. This gets the default hierachy only.
            /// </summary>
            /// <returns>the default log4net hierachy</returns>
            private Hierarchy hierachy()
            {
                Hierarchy h = (Hierarchy)log4net.LogManager.GetRepository();
                return h;
            }
            /*****************************************************************************/
            /// <summary>
            /// Creates and applies some configuration to the ado appender that we will be using
            /// with log4net. All we do here is basically assign the connection string.
            /// </summary>
            /// <returns></returns>
            private AdoNetAppender adoAppender()
            {
                Hierarchy myHierarchy = hierachy();
                if (myHierarchy != null)
                {
                    AdoNetAppender adoApp = (AdoNetAppender)myHierarchy.Root.GetAppender("ADONetAppender");
                    return adoApp;
                }
                return null;
            }
            /*****************************************************************************/
            /// <summary>
            /// Applies the connection string and activates the options on our ADO appender.
            /// Note that we can alter configuration here also - but the configuration in the app.config
            /// belonging to the driving application will override any that is placed here. By altering
            /// the configuration here, we loose some of the flexibility provided by log4net.
            /// </summary>
            /// <param name="dbConn"></param>
            private void createAdoSettings(string dbConn)
            {
                AdoNetAppender myAppender = adoAppender();
                if (myAppender != null)
                {
                    myAppender.ConnectionString = dbConn;
                    myAppender.UseTransactions = true;
                    myAppender.ActivateOptions();
                }
            }
        }
    }
    for this to work in an app, you are going to want the following code (or something very similar) in your app.config (I am assuming that if you are reading this, you have already set up your app.config for log4net):

    Code:
    <log4net>
        <root>
          <level value="DEBUG" />
          <appender-ref ref="ConsoleAppender" />
          <appender-ref ref="ADONetAppender" />
        </root>
        <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
          <layout type="log4net.Layout.PatternLayout">
            <conversionPattern value="%date [%thread] %-5level %message%newline %L %C" />
          </layout>
        </appender>
        <appender name="ADONetAppender" type="log4net.Appender.ADONetAppender">
          <connectionType value="MySql.Data.MySqlClient.MySqlConnection, MySql.Data, Version=6.0.3.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d" />
          <commandText value="insert into mylog (thread,line,object_ref,taskID)
                       values(?thread,?line,?objectref,?taskid)" />
          <parameter>
            <parameterName value="?thread"/>
            <dbType value="String"/>
            <size value="20"/>
            <layout type="log4net.Layout.PatternLayout">
              <conversionPattern value="%thread"/>
            </layout>
          </parameter>
          <parameter>
            <parameterName value="?line"/>
            <dbType value="String"/>
            <size value="20"/>
            <layout type="log4net.Layout.PatternLayout">
              <conversionPattern value="%L"/>
            </layout>
          </parameter>
          <parameter>
            <parameterName value="?objectref"/>
            <dbType value="String"/>
            <size value="20"/>
            <layout type="log4net.Layout.PatternLayout">
              <conversionPattern value="%C"/>
            </layout>
          </parameter>
          <parameter>
            <parameterName value="?taskid"/>
            <dbType value="String"/>
            <size value="20"/>
            <layout type="log4net.Layout.PatternLayout">
              <conversionPattern value="%property{taskid}"/>
            </layout>
          </parameter>
        </appender>
      </log4net>
    You can ignore the console appender for now, its the adoappender thats key at this point.

    Now we have all the code needed to start logging away to MySQL - to make use of it just reference it in the app you wish to log with, here is the test client I used to make sure this worked for me:

    Code:
    using Logger.Setup;
    using System;
    using System.Configuration;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace TestClient
    {
        class Program
        {
            protected static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
            
            static void Main(string[] args)
            {
                Program test = new Program();
                test.logtest();          
            }
            private void logtest()
            {
                log4net.GlobalContext.Properties["taskid"] = "89";
                string connstring = ConfigurationManager.ConnectionStrings["LogDB"].ConnectionString.ToString();
                SetUpLog _setup = new SetUpLog(connstring);
                Console.WriteLine(connstring);
                log.Info("Hello there");
            }
        }
    }
    As you can see, I have set a property with the value of "89", this is just me testing out this particular peice of functionality. As it stands right now, the log will run and update my database with the details I specify - but, there is one non-fatal error:

    Code:
    log4net:ERROR [AdoNetAppender] Could not open database connection []
    System.ArgumentNullException: Key cannot be null.
    Parameter name: key
       at System.Collections.Hashtable.get_Item(Object key)
       at MySql.Data.MySqlClient.MySqlPoolManager.GetPool(MySqlConnectionStringBuild
    er settings)
       at MySql.Data.MySqlClient.MySqlConnection.Open()
       at log4net.Appender.AdoNetAppender.InitializeDatabaseConnection()
    Server=localhost;Database=logDB;Uid=root;Pwd=password;
    2009-05-20 22:37:31,661 [1] INFO  Hello there
     27 TestClient.Program
    You see, my log runs but gives out this error and I cannot for the life of me figure out where it is coming from. I have been all over my app.config and checked it against similar examples on the web, all with no joy. I have also asked the log4net mailing list, but either noone is that interested or there is little activity on it.

    I am pretty certain its just a config issue - but I cannot find out where to look to get it sorted... If anyone else has a clue of where I can look, I would be very grateful.

    I am going to be blogging this soon (I would have blogged it first and posted the link, but I am not allowed to yet).
  8. #5
  9. No Profile Picture
    Registered User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Sep 2012
    Posts
    1
    Rep Power
    0

    Thanks


    Your post has been very helpful with getting Log4Net setup and running. I did encounter an error in the app.config file. It seems that it wants to see the parameter "connectionstring" in there, otherwise it will throw an error. I don't know if this has to do w/ the mysterious error you're seeing and trying to resolve. I enabled debugging and it complained missing that parameter or it being NULL. After I added it it, it worked w/o errors.

    Originally Posted by karym6
    No worries, log4net is very powerful, but quite hard to get to grips with imo, so it is only fair I share what I have been able to do...

    Ok, this is my working soloution right now - with one small caveat, it generates a non fatal error.

    below is some code you can use for a completee class to handle logging with log4net, it works no problems.

    Code:
    using log4net;
    using log4net.Appender;
    using log4net.Repository.Hierarchy;
    using System;
    using System.Collections.Generic;
    using MySql.Data.MySqlClient;
    using System.Linq;
    using System.Text;
    
    namespace Logger.Setup
    {
        public class SetUpLog
        {
            /*****************************************************************************/
            /// <summary>
            /// Use this constructor if you only want to log to the console or to a file.
            /// </summary>
            internal SetUpLog()
            {
                log4net.Config.XmlConfigurator.Configure();
            }
            /*****************************************************************************/
            /// <summary>
            /// Use this constructor if you want to log to a database. If you choose this option
            /// you need to make sure that the application driving the process has the correct 
            /// configuration in its app.config
            /// </summary>
            /// <param name="dbConn">string representing the data base connection</param>
            public SetUpLog(string dbConn)
            {
                log4net.Config.XmlConfigurator.Configure();
                createAdoSettings(dbConn);
            }
            /*****************************************************************************/
            /// <summary>
            /// Defines a hierachy for use with log4net. This gets the default hierachy only.
            /// </summary>
            /// <returns>the default log4net hierachy</returns>
            private Hierarchy hierachy()
            {
                Hierarchy h = (Hierarchy)log4net.LogManager.GetRepository();
                return h;
            }
            /*****************************************************************************/
            /// <summary>
            /// Creates and applies some configuration to the ado appender that we will be using
            /// with log4net. All we do here is basically assign the connection string.
            /// </summary>
            /// <returns></returns>
            private AdoNetAppender adoAppender()
            {
                Hierarchy myHierarchy = hierachy();
                if (myHierarchy != null)
                {
                    AdoNetAppender adoApp = (AdoNetAppender)myHierarchy.Root.GetAppender("ADONetAppender");
                    return adoApp;
                }
                return null;
            }
            /*****************************************************************************/
            /// <summary>
            /// Applies the connection string and activates the options on our ADO appender.
            /// Note that we can alter configuration here also - but the configuration in the app.config
            /// belonging to the driving application will override any that is placed here. By altering
            /// the configuration here, we loose some of the flexibility provided by log4net.
            /// </summary>
            /// <param name="dbConn"></param>
            private void createAdoSettings(string dbConn)
            {
                AdoNetAppender myAppender = adoAppender();
                if (myAppender != null)
                {
                    myAppender.ConnectionString = dbConn;
                    myAppender.UseTransactions = true;
                    myAppender.ActivateOptions();
                }
            }
        }
    }
    for this to work in an app, you are going to want the following code (or something very similar) in your app.config (I am assuming that if you are reading this, you have already set up your app.config for log4net):

    Code:
    <log4net>
        <root>
          <level value="DEBUG" />
          <appender-ref ref="ConsoleAppender" />
          <appender-ref ref="ADONetAppender" />
        </root>
        <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
          <layout type="log4net.Layout.PatternLayout">
            <conversionPattern value="%date [%thread] %-5level %message%newline %L %C" />
          </layout>
        </appender>
        <appender name="ADONetAppender" type="log4net.Appender.ADONetAppender">
          <connectionType value="MySql.Data.MySqlClient.MySqlConnection, MySql.Data, Version=6.0.3.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d" />
          <commandText value="insert into mylog (thread,line,object_ref,taskID)
                       values(?thread,?line,?objectref,?taskid)" />
          <parameter>
            <parameterName value="?thread"/>
            <dbType value="String"/>
            <size value="20"/>
            <layout type="log4net.Layout.PatternLayout">
              <conversionPattern value="%thread"/>
            </layout>
          </parameter>
          <parameter>
            <parameterName value="?line"/>
            <dbType value="String"/>
            <size value="20"/>
            <layout type="log4net.Layout.PatternLayout">
              <conversionPattern value="%L"/>
            </layout>
          </parameter>
          <parameter>
            <parameterName value="?objectref"/>
            <dbType value="String"/>
            <size value="20"/>
            <layout type="log4net.Layout.PatternLayout">
              <conversionPattern value="%C"/>
            </layout>
          </parameter>
          <parameter>
            <parameterName value="?taskid"/>
            <dbType value="String"/>
            <size value="20"/>
            <layout type="log4net.Layout.PatternLayout">
              <conversionPattern value="%property{taskid}"/>
            </layout>
          </parameter>
        </appender>
      </log4net>
    You can ignore the console appender for now, its the adoappender thats key at this point.

    Now we have all the code needed to start logging away to MySQL - to make use of it just reference it in the app you wish to log with, here is the test client I used to make sure this worked for me:

    Code:
    using Logger.Setup;
    using System;
    using System.Configuration;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace TestClient
    {
        class Program
        {
            protected static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
            
            static void Main(string[] args)
            {
                Program test = new Program();
                test.logtest();          
            }
            private void logtest()
            {
                log4net.GlobalContext.Properties["taskid"] = "89";
                string connstring = ConfigurationManager.ConnectionStrings["LogDB"].ConnectionString.ToString();
                SetUpLog _setup = new SetUpLog(connstring);
                Console.WriteLine(connstring);
                log.Info("Hello there");
            }
        }
    }
    As you can see, I have set a property with the value of "89", this is just me testing out this particular peice of functionality. As it stands right now, the log will run and update my database with the details I specify - but, there is one non-fatal error:

    Code:
    log4net:ERROR [AdoNetAppender] Could not open database connection []
    System.ArgumentNullException: Key cannot be null.
    Parameter name: key
       at System.Collections.Hashtable.get_Item(Object key)
       at MySql.Data.MySqlClient.MySqlPoolManager.GetPool(MySqlConnectionStringBuild
    er settings)
       at MySql.Data.MySqlClient.MySqlConnection.Open()
       at log4net.Appender.AdoNetAppender.InitializeDatabaseConnection()
    Server=localhost;Database=logDB;Uid=root;Pwd=password;
    2009-05-20 22:37:31,661 [1] INFO  Hello there
     27 TestClient.Program
    You see, my log runs but gives out this error and I cannot for the life of me figure out where it is coming from. I have been all over my app.config and checked it against similar examples on the web, all with no joy. I have also asked the log4net mailing list, but either noone is that interested or there is little activity on it.

    I am pretty certain its just a config issue - but I cannot find out where to look to get it sorted... If anyone else has a clue of where I can look, I would be very grateful.

    I am going to be blogging this soon (I would have blogged it first and posted the link, but I am not allowed to yet).

IMN logo majestic logo threadwatch logo seochat tools logo