219 lines
9.8 KiB
C#
219 lines
9.8 KiB
C#
/*============================================================================
|
||
Summary: Contains class implementiong xEvent data logging for Azure Analysis Services
|
||
Copyright (C) Microsoft Corporation.
|
||
|
||
|
||
This source code is intended only as a supplement to Microsoft
|
||
Development Tools and/or on-line documentation.
|
||
|
||
THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
|
||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
||
IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
|
||
PARTICULAR PURPOSE.
|
||
============================================================================*/
|
||
using System;
|
||
using System.Xml;
|
||
using System.Threading;
|
||
using System.IO;
|
||
using System.Text;
|
||
using Microsoft.AnalysisServices;
|
||
using Microsoft.AnalysisServices.AdomdClient;
|
||
using Microsoft.SqlServer.XEvent.Linq; // Referenced from the GAC version of Microsoft.SqlServer.XEvent.Linq
|
||
|
||
namespace TracingSample
|
||
{
|
||
public class Worker
|
||
{
|
||
private string UserName;
|
||
private string AsServer;
|
||
private string AsDatabase;
|
||
private string eventTmsl;
|
||
private string logFile;
|
||
|
||
public Worker(string user, string server, string db, string events, string log)
|
||
{
|
||
UserName = user;
|
||
AsServer = server;
|
||
AsDatabase = db;
|
||
eventTmsl = events;
|
||
logFile = log;
|
||
}
|
||
|
||
// This method will be called when the thread is started.
|
||
public void DoWork()
|
||
{
|
||
try
|
||
{
|
||
using (Server server = new Server())
|
||
{
|
||
//Connect and get main objects
|
||
string serverConnectionString;
|
||
|
||
// Assume integratedAuth
|
||
// otherwise serverConnectionString = $"Provider=MSOLAP;Data Source={AsServer};User ID={UserName};Password={Password};Impersonation Level=Impersonate;";
|
||
serverConnectionString = $"Provider=MSOLAP;Data Source={AsServer};Integrated Security=SSPI";
|
||
server.Connect(serverConnectionString);
|
||
|
||
Database database = server.Databases.FindByName(AsDatabase);
|
||
|
||
if (database == null)
|
||
{
|
||
throw new Microsoft.AnalysisServices.ConnectionException($"Could not connect to database {AsDatabase}.");
|
||
}
|
||
|
||
//Register the events you want to trace
|
||
string queryString = System.IO.File.ReadAllText(eventTmsl);
|
||
server.Execute(queryString);
|
||
|
||
// Now you need to subscribe to the xEvent stream and execute a data reader
|
||
// You need to have the same name as in the TMSL file!
|
||
// NOTE calls to the reader will block until new values show up!
|
||
string sessionId = "SampleXEvents";
|
||
AdomdConnection conn = new AdomdConnection(serverConnectionString);
|
||
conn.Open();
|
||
|
||
var cmd = conn.CreateCommand();
|
||
|
||
cmd.CommandType = System.Data.CommandType.Text;
|
||
cmd.CommandText =
|
||
"<Subscribe xmlns=\"http://schemas.microsoft.com/analysisservices/2003/engine\">" +
|
||
"<Object xmlns=\"http://schemas.microsoft.com/analysisservices/2003/engine\">" +
|
||
"<TraceID>" + sessionId + "</TraceID>" +
|
||
"</Object>" +
|
||
"</Subscribe>";
|
||
|
||
XmlReader inputReader = XmlReader.Create(cmd.ExecuteXmlReader(), new XmlReaderSettings() { Async = true });
|
||
|
||
//Connect to this with QueryableXEventData
|
||
using (QueryableXEventData data =
|
||
new QueryableXEventData(inputReader, EventStreamSourceOptions.EventStream, EventStreamCacheOptions.CacheToDisk))
|
||
{
|
||
|
||
using (FileStream fs = new FileStream(logFile, FileMode.Create, FileAccess.Write, FileShare.None))
|
||
{
|
||
//Write out the data in a long format for illustration
|
||
// this could would be adpated for your specific needs
|
||
foreach (PublishedEvent evt in data)
|
||
{
|
||
StringBuilder s = new StringBuilder();
|
||
s.Append($"Event: {evt.Name}\t");
|
||
s.Append(Environment.NewLine);
|
||
s.Append($"Timestamp: {evt.Timestamp}\t");
|
||
s.Append(Environment.NewLine);
|
||
|
||
foreach (PublishedEventField fld in evt.Fields)
|
||
{
|
||
s.Append($"Field: {fld.Name} = {fld.Value}\t");
|
||
s.Append(Environment.NewLine);
|
||
}
|
||
|
||
foreach (PublishedAction act in evt.Actions)
|
||
{
|
||
s.Append($"Action: {act.Name} = {act.Value}\t");
|
||
s.Append(Environment.NewLine);
|
||
}
|
||
s.Append(Environment.NewLine);
|
||
|
||
//Write the data to a log file
|
||
// the format and sink should be changed for your proposes
|
||
byte[] bytes = Encoding.ASCII.GetBytes(s.ToString().ToCharArray());
|
||
fs.Write(bytes, 0, s.Length);
|
||
|
||
if (_shouldStop == true)
|
||
{
|
||
break;
|
||
}
|
||
// Writing a . to show progress
|
||
Console.Write(".");
|
||
|
||
//Uncomment this to output to the Console
|
||
//Console.WriteLine(s);
|
||
|
||
}
|
||
//TODO stop the trace !
|
||
fs.Close();
|
||
}
|
||
conn.Close();
|
||
|
||
//clean up the trace on exit -- or you can keep it running
|
||
//var stopCommand = conn.CreateCommand();
|
||
|
||
//stopCommand.CommandType = System.Data.CommandType.Text;
|
||
var stopCommand =
|
||
"<Execute xmlns = \"urn:schemas-microsoft-com:xml-analysis\">" +
|
||
"<Command>" +
|
||
"<Batch …>" +
|
||
"<Delete …>" +
|
||
// You need to have the same name as in the TMSL file!
|
||
"<Object><TraceID>"+sessionId+"</TraceID></Object>" +
|
||
"</Delete>" +
|
||
"<Batch …>" +
|
||
"<Command>" +
|
||
"<Properties></Properties>" +
|
||
"</Execute>";
|
||
|
||
server.Execute(queryString);
|
||
server.Disconnect();
|
||
}
|
||
}
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
//TODO: handle exceptions :-)
|
||
Console.WriteLine(e.ToString());
|
||
Console.WriteLine("There was an error. Verify the command-line parmaters. Press any key to exit.");
|
||
}
|
||
//Worker thread: terminating gracefully.
|
||
|
||
}
|
||
public void RequestStop()
|
||
{
|
||
_shouldStop = true;
|
||
}
|
||
// Volatile is used as hint to the compiler that this data
|
||
// member will be accessed by multiple threads.
|
||
private volatile bool _shouldStop;
|
||
}
|
||
|
||
class Program
|
||
{
|
||
static void Main(string[] args)
|
||
{
|
||
string UserName = "user@contoso.com";
|
||
string AsServer = "asazure://region.asazure.windows.net/myinstance";
|
||
string AsDatabase = "SalesBI";
|
||
string eventTmsl = @"C:\AsXEventSample\eventTmsl.xmla"; // location of the xEvents TMSL file you want to collect, you can create this with SSMS script out
|
||
string logFile = @"C:\AsXEventSample\aslog.txt"; //location of the outputfile
|
||
|
||
// Using a thread here as data comes in asychronously
|
||
// Create the thread object. This does not start the thread.
|
||
Worker workerObject = new Worker(UserName, AsServer, AsDatabase, eventTmsl, logFile);
|
||
Thread workerThread = new Thread(workerObject.DoWork);
|
||
|
||
// Start the worker thread.
|
||
workerThread.Start();
|
||
|
||
// For monitoring, this would be upgraded to a windows service
|
||
Console.WriteLine("Listening for trace events which are sent when there is trace activity.");
|
||
Console.WriteLine("Type the letter q and enter to quit. The process will exit after the next trace event is received.");
|
||
|
||
bool cont = true;
|
||
while (cont)
|
||
{
|
||
Thread.Sleep(1);
|
||
if (ConsoleKey.Q == Console.ReadKey().Key)
|
||
{
|
||
workerObject.RequestStop();
|
||
Console.WriteLine("\nStopping reader on next trace event received...");
|
||
cont = false;
|
||
}
|
||
}
|
||
|
||
//wait for the worker to exit
|
||
workerThread.Join();
|
||
Console.WriteLine($"File is stored at: {logFile}");
|
||
}
|
||
}
|
||
}
|
||
|